Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
30b2a6f
new navigation rail component, minor website redesign, and more tweaks
mrsproutt Aug 22, 2025
a2043f2
Merge branch 'KTibow:main' into main
mrsproutt Aug 22, 2025
fed6e85
ripple effect, demos.md, llms.txt
mrsproutt Aug 23, 2025
18cbee6
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Aug 23, 2025
49b12f2
actually export the components
mrsproutt Aug 23, 2025
57e0844
non spec colors for better contrast
mrsproutt Aug 23, 2025
96fbe02
navrail modal and sidesheet shadow, update demos.md
mrsproutt Aug 23, 2025
b4ea580
z-index organization
mrsproutt Aug 23, 2025
ca67ebe
more small tweaks
mrsproutt Aug 23, 2025
7121a41
ungoof css
mrsproutt Aug 23, 2025
15acfba
ripples
mrsproutt Aug 24, 2025
08de5d9
rename icon imports
mrsproutt Aug 24, 2025
3ca17e8
pointer events auto
mrsproutt Aug 24, 2025
4e9eefc
fix icon imports, generate demos, combine attributes
mrsproutt Aug 24, 2025
8166446
generating messes stuff up
mrsproutt Aug 24, 2025
ccbcb04
remove themed scrollbars
mrsproutt Aug 24, 2025
315d695
update demo
mrsproutt Aug 24, 2025
16ca778
fighting eslint, font stuff
mrsproutt Aug 24, 2025
74ee5fd
rtl support
mrsproutt Aug 24, 2025
56fa079
badge updates
mrsproutt Aug 24, 2025
3f28df7
fix <a> nesting, update demo
mrsproutt Aug 24, 2025
93833dd
font troubles, aria
mrsproutt Aug 24, 2025
6e88087
type button
mrsproutt Aug 24, 2025
e536ad3
types
mrsproutt Aug 24, 2025
0bb09d2
remove onclick
mrsproutt Aug 24, 2025
5332c79
remove onclick
mrsproutt Aug 24, 2025
99a5cb7
remove onclick
mrsproutt Aug 24, 2025
ced001f
color mixing
mrsproutt Aug 24, 2025
3d0a97c
color mixing
mrsproutt Aug 24, 2025
dc03e6f
color mixing
mrsproutt Aug 24, 2025
536885e
color mixing
mrsproutt Aug 24, 2025
6e87846
use util-easing
mrsproutt Aug 24, 2025
805b234
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Aug 24, 2025
037098e
Merge branch 'KTibow:main' into main
mrsproutt Aug 24, 2025
1cd0dd9
new alignments
mrsproutt Aug 24, 2025
815ff87
demos and small tweaks
mrsproutt Aug 24, 2025
ae72d61
wip: some animations, progress on menutoggle
mrsproutt Aug 27, 2025
024a9b7
finish navigationtoggle
mrsproutt Aug 27, 2025
ef6df3f
Merge branch 'KTibow:main' into main
mrsproutt Aug 27, 2025
79e215c
color fixes based off figma design
mrsproutt Aug 27, 2025
15cb131
small tweaks
mrsproutt Aug 27, 2025
10a84bb
make the links span the full width
mrsproutt Aug 27, 2025
39fc5b5
more tweaks
mrsproutt Aug 27, 2025
abfca0a
fix mobile support and centering
mrsproutt Aug 28, 2025
38046d1
figma saves the day (again)
mrsproutt Aug 28, 2025
fdcf17b
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Aug 28, 2025
975453e
wraping and centering text
mrsproutt Aug 28, 2025
79d17a7
a11y is horrible
mrsproutt Aug 30, 2025
46c0e73
Merge branch 'main' into main
mrsproutt Aug 30, 2025
c3b36f9
use addBadge
mrsproutt Aug 30, 2025
9e8da13
Merge branch 'KTibow:main' into main
mrsproutt Sep 2, 2025
14cd33c
Merge branch 'KTibow:main' into main
mrsproutt Sep 4, 2025
67dfaf6
prevent duplicate navigationtoggle ids
mrsproutt Sep 4, 2025
94d280d
wip: animations
mrsproutt Sep 6, 2025
d12663f
fix stuff caused by animations
mrsproutt Sep 6, 2025
ebb4f92
remove layout changes per request
mrsproutt Nov 4, 2025
8368060
how was I supposed to know that button did
mrsproutt Nov 4, 2025
c7020f8
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Nov 4, 2025
f46eead
Revert "how was I supposed to know that button did"
mrsproutt Nov 4, 2025
59e6085
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Nov 4, 2025
ab2be7f
ok, I genuinely have no fucking clue what is going on
mrsproutt Nov 4, 2025
d70b359
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Nov 4, 2025
13b0997
im digging my own grave
mrsproutt Nov 4, 2025
3565cae
please god
mrsproutt Nov 4, 2025
ea3b9c1
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Nov 4, 2025
027e1b3
holdon now
mrsproutt Nov 4, 2025
e63f720
Merge branch 'KTibow:main' into main
mrsproutt Nov 4, 2025
056aa91
lets get this thing merged
mrsproutt Dec 28, 2025
adbc0be
merging as much as I can
mrsproutt Dec 28, 2025
7dbe16e
get everything working with the new changes
mrsproutt Dec 30, 2025
fd5e180
Merge branch 'KTibow:main' into main
mrsproutt Dec 30, 2025
fe35605
small tweaks and fixes
mrsproutt Dec 30, 2025
a684556
m3 svelte 7!!!
mrsproutt Dec 30, 2025
3d4d406
update to v7
mrsproutt Dec 30, 2025
b4e584e
Merge branch 'KTibow:main' into main
mrsproutt Dec 31, 2025
17ed98b
revert changes and fix navrail item animation
mrsproutt Dec 31, 2025
d5b8458
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Dec 31, 2025
2332f13
reorder demo
KTibow Dec 31, 2025
f669be8
Restore trailing newlines in HTML/Svelte files
KTibow Dec 31, 2025
8a38ed6
Revert eslint config changes and remove unused divider prop
KTibow Dec 31, 2025
56ce8b9
restyling demo
KTibow Dec 31, 2025
daa4c91
Merge remote-tracking branch 'upstream/main'
KTibow Dec 31, 2025
5ab21f7
Merge remote-tracking branch 'upstream/main'
KTibow Dec 31, 2025
3b5511b
lint and format
KTibow Dec 31, 2025
e1c3b3c
Update NavigationToggle.svelte
KTibow Dec 31, 2025
f8ceb64
Merge remote-tracking branch 'upstream/main'
KTibow Dec 31, 2025
16d9b7d
don't overcomplicate fab
KTibow Dec 31, 2025
a1544fe
minor style
KTibow Dec 31, 2025
aee4ac9
NavigationToggle is internal
KTibow Dec 31, 2025
3f12b92
use modern typing utils
KTibow Dec 31, 2025
65413f8
???
KTibow Dec 31, 2025
9f68c6c
Merge remote-tracking branch 'upstream/main'
KTibow Dec 31, 2025
3be90a6
Merge branch 'KTibow:main' into main
mrsproutt Jan 1, 2026
d8f3b61
tweaks
mrsproutt Jan 1, 2026
0eccddb
Merge branch 'main' of https://github.com/mrsproutt/m3-svelte
mrsproutt Jan 1, 2026
21f8b8e
Merge branch 'KTibow:main' into main
mrsproutt Jan 17, 2026
8c1dbe3
Merge branch 'KTibow:main' into main
mrsproutt Jan 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/demos.md
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,86 @@ let item = $state("a");
</style>
```

## Navigation Rail

Minimal demo:

```svelte
<NavigationRail>
{#snippet fab(open)}
<FAB
color="primary-container"
text={open ? "Label" : undefined}
elevation="none"
onclick={() => alert("!")}
/>
{/snippet}

<NavigationRailItem label="Label" icon={iconStars} active />

<NavigationRailItem label="Label" icon={iconStarsOutline} />

<NavigationRailItem label="Label" icon={addBadge(iconStarsOutline, 3)} />

<NavigationRailItem label="Label" icon={addBadge(iconStarsOutline)} />
</NavigationRail>
```

Full demo:

```use
FAB
NavigationRail
NavigationRailItem
```

```ts
import iconCircleFilled from "@ktibow/iconset-material-symbols/circle";
import iconEdit from "@ktibow/iconset-material-symbols/edit";
import { addBadge } from "$lib/misc/badge";

let collapse: "full" | "normal" | "no" = $state("normal");
let alignment: "top" | "center" = $state("center");
let iconType: "full" | "left" = $state("left");
let modal = $state(false);
let open = $state(false);
```

```svelte
<label>
<Switch bind:checked={modal} />
{modal ? "Modal" : "Normal"}
</label>
<label>
<Arrows list={["left", "full"]} bind:value={iconType} />
{iconType == "left" ? "Icon and text" : "Icon"}
</label>
<label>
<Arrows list={["top", "center"]} bind:value={alignment} />
{alignment == "top" ? "Top" : "Center"}
</label>
<label>
<Arrows list={["normal", "full", "no"]} bind:value={collapse} />
{collapse == "normal" ? "Collapse" : collapse == "full" ? "Fully collapse" : "Don't collapse"}
</label>

{#snippet demo()}
<NavigationRail {collapse} {alignment} {iconType} {modal} {open}>
{#snippet fab(open)}
<FAB
color="primary-container"
icon={iconEdit}
text={open ? "Label" : undefined}
elevation="none"
onclick={() => {}}
/>
{/snippet}
<NavigationRailItem label="Label" icon={iconCircleFilled} active />
<NavigationRailItem label="Label" icon={addBadge(iconTriangle, 3)} />
</NavigationRail>
{/snippet}
```

## UI transitions

Minimal demo:
Expand Down
20 changes: 15 additions & 5 deletions src/lib/buttons/FAB.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import type { IconifyIcon } from "@iconify/types";
import Icon from "$lib/misc/Icon.svelte";
import type { ButtonAttrs } from "$lib/misc/typing-utils";
import { slide } from "svelte/transition";
import { easeEmphasized } from "$lib/misc/easing";

type ContentProps =
| {
Expand Down Expand Up @@ -50,7 +52,7 @@
<Icon {icon} size={size == "large" ? 36 : size == "medium" ? 28 : 24} />
{/if}
{#if text}
{text}
<span transition:slide={{ axis: "x", duration: 500, easing: easeEmphasized }}>{text}</span>
{/if}
</button>

Expand Down Expand Up @@ -84,30 +86,38 @@
.size-small {
height: 2.5rem;
padding-inline: 0.5rem;
gap: 0.5rem;
border-radius: var(--m3-fab-small-shape);
}
.size-small > :global(svg + span) {
margin-inline-start: 0.5rem;
}
.size-normal {
@apply --m3-title-medium;
height: 3.5rem;
padding-inline: 1rem;
gap: 0.5rem;
border-radius: var(--m3-fab-normal-shape);
}
.size-normal > :global(svg + span) {
margin-inline-start: 0.5rem;
}
.size-medium {
@apply --m3-title-large;
height: 5rem;
padding-inline: 1.625rem;
gap: 0.75rem;
border-radius: var(--m3-fab-medium-shape);
}
.size-medium > :global(svg + span) {
margin-inline-start: 0.75rem;
}
.size-large {
@apply --m3-headline-small;
height: 6rem;
padding-inline: 1.75rem;
gap: 1rem;
border-radius: var(--m3-fab-large-shape);
}
.size-large > :global(svg + span) {
margin-inline-start: 1rem;
}

.color-primary {
background-color: var(--m3c-primary);
Expand Down
2 changes: 2 additions & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export { default as WavyLinearProgressEstimate } from "./forms/WavyLinearProgres

export { default as NavCMLX } from "./nav/NavCMLX.svelte";
export { default as NavCMLXItem } from "./nav/NavCMLXItem.svelte";
export { default as NavigationRail } from "./nav/NavigationRail.svelte";
export { default as NavigationRailItem } from "./nav/NavigationRailItem.svelte";
export { default as Tabs } from "./nav/Tabs.svelte";
export { default as TabsLink } from "./nav/TabsLink.svelte";
export { default as VariableTabs } from "./nav/VariableTabs.svelte";
Expand Down
185 changes: 185 additions & 0 deletions src/lib/nav/NavigationRail.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<script lang="ts">
import type { Snippet } from "svelte";

import NavigationToggle from "./NavigationToggle.svelte";

let {
open = $bindable(false),
collapse = "normal",
modal = false,
alignment = "top",
iconType = "left",
fab,
children,
}: {
open?: boolean;
collapse?: "normal" | "full" | "no" | boolean;
modal?: boolean;
alignment?: "top" | "center";
iconType?: "left" | "full";
fab?: Snippet<[open: boolean]>;
children: Snippet;
} = $props();

const onkeydown = (e: KeyboardEvent) => {
if (modal && open && e.key == "Escape") {
e.preventDefault();

open = false;
}
};
</script>

<svelte:window {onkeydown} />

<div class="m3-container">
<div
class:rail={true}
class:open
class:centered={alignment == "center"}
class:collapse={collapse == "full"}
class:icon={iconType == "full"}
class:modal
>
{#if (collapse != "no" && collapse != false) || fab}
<div class="top">
{#if collapse != "no" && collapse != false}
<NavigationToggle mode={collapse == "full" ? "inline-detached" : "inline"} bind:open />
{/if}

{#if fab}
<div>
{@render fab(open)}
</div>
{/if}
</div>
{/if}

<div class="items" role="menu" aria-labelledby="m3-navigationtoggle">
{@render children()}
</div>
</div>

{#if modal}
<!--svelte-ignore a11y_no_static_element_interactions--><!--svelte-ignore a11y_click_events_have_key_events-->
<div class="shadow" onclick={() => (open = false)}></div>
{/if}
</div>

<style>
.m3-container {
width: 96px;
height: 100%;
transition: width var(--m3-easing-spatial);
}

.m3-container:has(> .rail.collapse) {
width: 0;
}

.rail.open,
.m3-container:has(> .rail.open:not(.modal)),
.rail.collapse {
width: 220px;
}

.rail {
display: flex;
flex-direction: column;
gap: 40px;
width: 96px;
height: 100%;
padding: 44px 0px 56px 0px;
transition: all var(--m3-easing-spatial);
overflow: hidden;
overflow-y: auto;
scrollbar-width: thin;
}

.rail.open,
.rail.icon {
gap: 32px;
}

.rail:not(.open).collapse {
width: 0px;
}

.rail:not(.open).collapse > :global(*:not(.top)),
.rail:not(.open).collapse .top > :global(*:not(.toggle)) {
pointer-events: none;
}

.rail:not(.open).collapse > .top > :not(:global(.toggle)),
.rail:not(.open).collapse > .items {
opacity: 0;
}

.rail.modal {
border-start-end-radius: var(--m3-shape-large);
border-end-end-radius: var(--m3-shape-large);
}

.rail.modal.open,
.rail:not(.modal) {
background: var(--m3c-surface-container);
}

.rail.collapse > .top {
margin-top: 56px;
}

.top {
margin-inline: 20px;
display: flex;
flex-direction: column;
z-index: 1;
}

.items {
display: flex;
flex-direction: column;
gap: 12px;
width: 96px;
height: 100%;
align-self: stretch;
transition:
gap var(--m3-easing),
width 0.2s;
height: 100%;
}

.rail.open > .items,
.rail.collapse > .items {
gap: 0px;
width: 220px;
}

.rail.icon > .items {
gap: 0px;
}

.rail.centered > .items {
justify-content: center;
}

.shadow {
position: fixed;
inset: 0;
z-index: -1;
background-color: --translucent(var(--m3c-scrim), 0.5);
transition: opacity var(--m3-easing);
}

.rail:not(.open) + .shadow {
opacity: 0;
pointer-events: none;
}

@media (width <= 300px) {
.rail.open {
border-radius: 0px !important;
width: 100vw;
}
}
</style>
Loading