If layers don't work on your own components, or even in M3 Svelte's, you may need to import "m3-svelte/etc/layer".
`}
/>
{:else}
{/if}
So what's going on here?
We're using M3 Svelte globals.
Some globals are defined in styles.css . If you want to change them, nothing's stopping you from overriding them in your own theme.
Other globals are defined directly in your theme - you can read and write them there.
Tokens
These live in @layer tokens. M3 Svelte defines many, like
elevations
(--m3-elevation-0 through
--m3-elevation-5) and
shapes (--m3-shape-[size]
where size is none, extra-small, small, medium, large, extra-large, or full).
Colors , the
--m3c- variables, are also tokens.
Want these to be minified and inlined? The "Use token-shaker" option from setup does that.
Variables
These start with --m3v-, and let you send information to M3 Svelte.
Set --m3v-bottom-offset to shift up snackbars
Set --m3v-background to calibrate the color of an outlined field's box
Functions
These are shorthands for specific logic, like:
--translucent([color], [opacity]) makes a color semitransparent
--m3-density([size]) adjusts a size
Mixins
These are shorthands for specific properties, applied with @apply --[name].
M3 Svelte's built in mixins: font styles
(@apply --m3-[scale]-[size]. scales: display, headline, title,
body, label. sizes: large, medium, small) and focus styles
(@apply --m3-focus-inward and
@apply --m3-focus-none).
```
## Tips
M3 Svelte is more and less than the typical component framework.
On one hand, Material 3 consists of guidelines across color, shadow, easing, animation, and spacing, and M3 Svelte is all of that. You don't have to make your own components or animations - you could use components like `Card` and animate them with the `sharedAxisTransition`, eased with `easeEmphasized`.
On the other hand, M3 Svelte doesn't give you a template. There are many things you have to do yourself, like:
- Generating and pasting your theme
- Including your font
- Resetting CSS
- Setting up your own layout (ideally a responsive one)
- Styling your components (if you need something M3 doesn't have)
- Slotting in s
- Rendering icons
If you're ever unsure about something, you might want to check the [source code](https://github.com/KTibow/m3-svelte). The M3 Svelte demo website is built with M3 Svelte and might have implemented what you're trying to do.
## Troubleshooting
You should avoid trying to restyle components. You can't set a `class` and due to Svelte scoped styles, even if you could, it wouldn't work. I repeat, you CANNOT set a `class` on M3 Svelte components. Always look for alternatives, like using `gap` instead of `margin`. If you _must_ do this, you would have to do something like `.my-container > :global(.m3-container)`.
You must wrap Checkbox, Radio, and Switch in s.
M3 Svelte uses the Iconify ecosystem. M3 Svelte imports icons from NPM and renders them as SVG. Some components render icons for you:
```svelte
```
While in other cases, you render them yourselves:
```svelte
{#snippet leading()}
{/snippet}
```
## Examples
Here's an example todo list component:
```svelte
{#each tasks as task (task.id)}
(tasks = tasks.filter((t) => t.id != task.id))}
>
{/each}
```
Here's an example notes app:
```svelte
{#if notes.length}
{#each notes as note (note.id)}
(dialog = {
open: true,
editingId: note.id,
title: note.title,
content: note.content,
})}
>
deleteNote(note.id)}>
{/each}
{:else}
No notes yet
Click the + button to create one
{/if}
{#snippet buttons()}
Cancel
{#if dialog.editingId}
Update
{:else}
Add
{/if}
{/snippet}
```
Here's an even more complicated example - a partial clone of the M3 documentation website, using SvelteKit:
```css layout.css
@import url("https://fonts.googleapis.com/css2?family=Google+Sans+Flex:opsz,wght@6..144,400..700&display=swap");
/* snip: theme snippet */
:root {
@apply --m3-body-large;
background: var(--m3c-surface);
color: var(--m3c-on-surface);
}
body {
display: flex;
height: 100dvh;
margin: 0;
box-sizing: border-box;
}
@media (width < 52.5rem) {
body {
flex-direction: column-reverse;
--m3v-bottom-offset: 5rem;
}
}
img {
max-width: 100%;
}
/* not implemented: a comprehensive css reset */
```
```svelte +layout.svelte
{}} />
{#each navItems as item}
{/each}
```
```svelte +page.svelte
{#each tabs as tab}
(currentTab = tab.id)}>
{tab.label}
{/each}
{Array.from({ length: 200 }, () => "Lorem ipsum.").join(" ")}
```
## Components
Okay, now on to the components. Again, if you're unsure about how a component works, you can look at [its source code](https://github.com/KTibow/m3-svelte).
### Button
Minimal demo:
```svelte
alert("!")}>Hello
```
The M3 Svelte interactive demo for this component uses Button with the TS `let variant: "elevated" | "filled" | "tonal" | "outlined" | "text" = $state("filled");
let action: "click" | "link" | "toggle" = $state("click");
let square = $state(false);
let iconType: "none" | "left" | "full" = $state("none");
const sizes = ["xs", "s", "m", "l", "xl"] as const;
const sizeLabels = ["Extra small", "Small", "Medium", "Large", "Extra large"] as const;
let sizeIndex = $state(1);
let enabled = $state(true);` to make this demo:
```svelte
{variant[0].toUpperCase() + variant.slice(1)}
{action[0].toUpperCase() + action.slice(1)}
{square ? "Square" : "Round"}
{iconType == "none" ? "No icon" : iconType == "left" ? "Left icon" : "Icon"}
sizeLabels[n]} />
{enabled ? "Enabled" : "Disabled"}
{#snippet demo()}
{}, disabled: !enabled },
link: { href: "https://example.com" },
toggle: { label: true },
}[action]}
{iconType}
>
{#if action == "toggle"}
{/if}
{#if iconType == "none"}
Hello
{:else if iconType == "left"}
Hello
{:else}
{/if}
{/snippet}
```
### Connected buttons
Minimal demo:
```svelte
A
B
```
The M3 Svelte interactive demo for this component uses ConnectedButtons, Button with the TS `let variant: "filled" | "tonal" = $state("filled");
let multiselect = $state(true);
const sizes = ["xs", "s", "m", "l", "xl"] as const;
const sizeLabels = ["Extra small", "Small", "Medium", "Large", "Extra large"] as const;
let sizeIndex = $state(1);` to make this demo:
```svelte
{variant[0].toUpperCase() + variant.slice(1)}
{multiselect ? "Multi-select" : "Single-select"}
sizeLabels[n]} />
{#snippet demo()}
{@const size = sizes[sizeIndex]}
Alpha
Beta
Charlie
{/snippet}
```
### Split button
Minimal demo:
```svelte
alert("!")}>
Hello
{#snippet menu()}
and more
{/snippet}
```
The M3 Svelte interactive demo for this component uses SplitButton, Menu, MenuItem with the TS `let variant: "elevated" | "filled" | "tonal" | "outlined" = $state("filled");
let position: "inner-down" | "inner-up" | "right-down" | "right-up" = $state("inner-down");
let iconType: "none" | "left" | "full" = $state("none");` to make this demo:
```svelte
{variant[0].toUpperCase() + variant.slice(1)}
{position[0].toUpperCase() + position.slice(1).replace("-", " ")}
{iconType == "none" ? "No icon" : iconType == "left" ? "Left icon" : "Icon"}
{#snippet demo()}
{}}
>
{#if iconType == "none"}
Hello
{:else if iconType == "left"}
Hello
{:else}
{/if}
{#snippet menu()}
{}}>Alpha
{}}>Beta
{}}>Charlie
{/snippet}
{/snippet}
```
### FAB
Minimal demo:
```svelte
alert("!")} />
```
The M3 Svelte interactive demo for this component uses FAB with the TS `let color:
| "primary-container"
| "secondary-container"
| "tertiary-container"
| "primary"
| "secondary"
| "tertiary" = $state("primary-container");
const sizes = ["normal", "medium", "large"] as const;
let sizeIndex = $state(0);
let iconTextSetup: "icon" | "both" | "text" = $state("icon");` to make this demo:
```svelte
{color[0].toUpperCase() + color.slice(1).replace("-", " ")}
sizes[n][0].toUpperCase() + sizes[n].slice(1)}
/>
{iconTextSetup == "icon" ? "Icon only" : iconTextSetup == "both" ? "Icon and text" : "Text only"}
{#snippet demo()}
{@const size = sizes[sizeIndex]}
{}}
/>
{/snippet}
```
### Card
Minimal demo:
```svelte
Hello
alert("!")}>Hello
```
The M3 Svelte interactive demo for this component uses Card with the TS `let variant: "filled" | "outlined" | "elevated" = $state("filled");
let clickable = $state(false);` to make this demo:
```svelte
{variant[0].toUpperCase() + variant.slice(1)}
{clickable ? "Clickable" : "Not clickable"}
{#snippet demo()}
{} } : {}}>Hello
{/snippet}
```
### List
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses ListItem, Checkbox, Divider with the TS `const headline = "Hello";
let lines: "1" | "2" | "3" = $state("1");
let type: "div" | "button" | "label" | "a" = $state("div");
let supporting = $derived(
lines == "1"
? undefined
: lines == "2"
? "Welcome to ZomboCom!"
: "Welcome to ZomboCom! Anything is possible at ZomboCom! You can do anything at ZomboCom!",
);` to make this demo:
```svelte
{lines}
{lines == "1" ? "line" : "lines"}
{"<" + type + ">"}
{#snippet demo()}
{#snippet leading()}
{#if type == "label"}
{:else}
{/if}
{/snippet}
{} }
: type == "a"
? { href: "https://example.com" }
: {}}
/>
{} }
: type == "a"
? { href: "https://example.com" }
: {}}
/>
{/snippet}
```
### Menu
Minimal demo:
```svelte
Alpha
Beta
Charlie
```
The M3 Svelte interactive demo for this component uses Menu, MenuItem with the TS `let icons = $state(false);` to make this demo:
```svelte
{icons ? "Icons" : "No icons"}
{#snippet demo()}
{}}>Alpha
{}}>Beta
{}}>Charlie
{/snippet}
```
### Bottom sheet
Minimal demo:
```svelte
{#if open}
(open = false)}>Hello
{/if}
```
The M3 Svelte interactive demo for this component uses Button, BottomSheet with the TS `let open = $state(false);` to make this demo:
```svelte
{#snippet demo()}
(open = true)}>Open
{#if open}
(open = false)}>
{"Anything is possible at ZomboCom! You can do anything at ZomboCom! The infinite is possible at ZomboCom! The unattainable is unknown at ZomboCom! ".repeat(
20,
)}
{/if}
{/snippet}
```
### Dialog
Minimal demo:
```svelte
I'm alive
{#snippet buttons()}
OK
{/snippet}
```
The M3 Svelte interactive demo for this component uses Button, Dialog with the TS `let open = $state(false);` to make this demo:
```svelte
{#snippet demo()}
(open = true)}>Open
Anything is possible at ZomboCom! You can do anything at ZomboCom! The infinite is possible at
ZomboCom! The unattainable is unknown at ZomboCom!
{#snippet buttons()}
OK
{/snippet}
{/snippet}
```
### Snackbar
Minimal demo:
```svelte
snackbar("Hello", undefined, true)}>Show
```
The M3 Svelte interactive demo for this component uses Button, Snackbar with the TS `import { snackbar } from "$lib/containers/Snackbar.svelte";` to make this demo:
```svelte
{#snippet demo()}
snackbar("Hello", undefined, true)}>Show
{/snippet}
```
### Checkbox
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses Checkbox with the TS `let enabled = $state(true);` to make this demo:
```svelte
{enabled ? "Enabled" : "Disabled"}
{#snippet demo()}
{/snippet}
```
### Chip
Minimal demo:
```svelte
alert("!")}>Hello
```
The M3 Svelte interactive demo for this component uses Chip with the TS `let style: "input" | "assist" | "assist elevated" | "general" | "general elevated" =
$state("input");
let iconType: "none" | "left" | "right" = $state("none");
let enabled = $state(true);
let selected = $state(false);` to make this demo:
```svelte
{style[0].toUpperCase() + style.slice(1)}
{iconType == "none" ? "No icon" : iconType == "left" ? "Left icon" : "Right icon"}
{enabled ? "Enabled" : "Disabled"}
{#snippet demo()}
(selected = !selected)}
>
Hello
{/snippet}
```
### Progress
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses LinearProgress, LinearProgressEstimate, WavyLinearProgress, WavyLinearProgressEstimate, CircularProgress, CircularProgressEstimate, Button, Slider with the TS `let type: "linear" | "linear-wavy" | "circular" = $state("linear");
let estimate = $state(false);
let thick = $state(false);
let percent = $state(10);` to make this demo:
```svelte
{type[0].toUpperCase() + type.slice(1).replace("-", " ")}
{thick ? "Thicker" : "Thinner"}
{estimate ? "Estimated" : "Percent"}
{#if !estimate}
`${n.toFixed(0)}%`} />
{/if}
{#snippet demo()}
{#if estimate && type == "linear"}
{:else if estimate && type == "linear-wavy"}
{:else if estimate && type == "circular"}
{:else if type == "linear"}
{:else if type == "linear-wavy"}
{:else if type == "circular"}
{/if}
{/snippet}
```
### Loading
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses LoadingIndicator with the TS `let container = $state(false);` to make this demo:
```svelte
{container ? "Contained" : "Raw"}
{#snippet demo()}
{/snippet}
```
### Radio
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses RadioAnim1, RadioAnim2, RadioAnim3 with the TS `let animation: "1" | "2" | "3" = $state("1");
let enabled = $state(true);
let Component = $derived(
animation == "1" ? RadioAnim1 : animation == "2" ? RadioAnim2 : RadioAnim3,
);` to make this demo:
```svelte
Animation {animation}
{enabled ? "Enabled" : "Disabled"}
{#snippet demo()}
{/snippet}
```
### Select
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses Select, SelectOutlined with the TS `let variant: "filled" | "outlined" = $state("filled");
let icons = $state(false);
let enabled = $state(true);
let options = $derived([
{ icon: icons ? iconCircle : undefined, text: "Alpha", value: "alpha" },
{ icon: icons ? iconSquare : undefined, text: "Beta", value: "beta" },
{ icon: icons ? iconTriangle : undefined, text: "Charlie", value: "charlie" },
]);` to make this demo:
```svelte
{variant[0].toUpperCase() + variant.slice(1)}
{icons ? "Icons" : "No icons"}
{enabled ? "Enabled" : "Disabled"}
{#snippet demo()}
{#if variant === "filled"}
{:else}
{/if}
{/snippet}
```
### Slider
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses Slider with the TS `let precision = $state<"continuous" | "discrete" | "discrete-stops">("continuous");
const sizes = ["xs", "s", "m", "l", "xl"] as const;
const sizeLabels = ["Extra small", "Small", "Medium", "Large", "Extra large"] as const;
let sizeIndex = $state(0);
let trailingIcon = $state(false);
let leadingIcon = $state(false);
let endStops = $state(true);
let enabled = $state(true);
let vertical = $state(false);` to make this demo:
```svelte
{precision == "continuous"
? "Continuous"
: precision == "discrete"
? "Discrete"
: "Discrete (stops)"}
sizeLabels[n]} />
{enabled ? "Enabled" : "Disabled"}
{vertical ? "Vertical" : "Horizontal"}
{#if sizeIndex > 1}
{leadingIcon ? "Leading icon" : "No leading icon"}
{trailingIcon ? "Trailing icon" : "No trailing icon"}
{/if}
{#if precision != "discrete-stops" && !trailingIcon}
{endStops ? "Endstops" : "No endstops"}
{/if}
{#snippet demo()}
{/snippet}
```
### Switch
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses Switch with the TS `let icons: "checked" | "both" | "none" = $state("checked");
let enabled = $state(true);` to make this demo:
```svelte
{icons == "checked" ? "Checked icon" : icons == "both" ? "Both icons" : "No icons"}
{enabled ? "Enabled" : "Disabled"}
{#snippet demo()}
{/snippet}
```
### Text field
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses TextField, TextFieldOutlined, TextFieldMultiline, TextFieldOutlinedMultiline with the TS `let type: "filled" | "filled_multiline" | "outlined" | "outlined_multiline" = $state("filled");
let option: "text" | "password" | "number" | "file" = $state("text");
let leadingIcon = $state(false);
let trailingIcon = $state(false);
let errored = $state(false);
let enabled = $state(true);` to make this demo:
```svelte
{type == "filled"
? "Filled"
: type == "filled_multiline"
? "Filled multiline"
: type == "outlined"
? "Outlined"
: "Outlined multiline"}
{option == "text"
? "Text"
: option == "password"
? "Password"
: option == "number"
? "Number"
: "File"}
{leadingIcon ? "Leading icon" : "No leading icon"}
{trailingIcon ? "Trailing icon" : "No trailing icon"}
{errored ? "Errored" : "Not errored"}
{enabled ? "Enabled" : "Disabled"}
{#snippet demo()}
{#if type === "filled"}
{} } : undefined}
error={errored}
disabled={!enabled}
type={option}
/>
{:else if type === "outlined"}
{} } : undefined}
error={errored}
disabled={!enabled}
type={option}
/>
{:else if type === "filled_multiline"}
{:else if type === "outlined_multiline"}
{/if}
{/snippet}
```
### Date field
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses DateField, DateFieldOutlined with the TS `let variant: "filled" | "outlined" = $state("filled");
let enabled = $state(true);
let errored = $state(false);` to make this demo:
```svelte
{variant[0].toUpperCase() + variant.slice(1)}
{errored ? "Errored" : "Not errored"}
{enabled ? "Enabled" : "Disabled"}
{#snippet demo()}
{#if variant === "filled"}
{:else}
{/if}
{/snippet}
```
### Tabs
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses Tabs, VariableTabs with the TS `let type: "primary" | "secondary" = $state("primary");
let icons = $state(false);
let variable = $state(false);
let tab = $state("hello");
let items = $derived(
icons
? [
{ icon: iconCircle, name: "Hello", value: "hello" },
{ icon: iconSquare, name: "World", value: "world" },
{ icon: iconTriangle, name: "The longest item", value: "long" },
]
: [
{ name: "Hello", value: "hello" },
{ name: "World", value: "world" },
{ name: "The longest item", value: "long" },
],
);` to make this demo:
```svelte
{type == "primary" ? "Primary" : "Secondary"}
{icons ? "Icons" : "No icons"}
{variable ? "Variable" : "Fixed"}
{#snippet demo()}
{#if variable}
{:else}
{/if}
{/snippet}
```
### Nav for C/M/L/X
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses NavCMLX, NavCMLXItem with the TS `let variant: "compact" | "medium" | "large" | "extra-large" | "auto" = $state("auto");
let item = $state("a");` to make this demo:
```svelte
{variant[0].toUpperCase() + variant.slice(1).replace("-", " ")}
{#snippet demo()}
(item = "a")}
/>
(item = "b")}
/>
(item = "c")}
/>
{/snippet}
```
### Navigation Rail
Minimal demo:
```svelte
{#snippet fab(open)}
alert("!")}
/>
{/snippet}
```
The M3 Svelte interactive demo for this component uses FAB, NavigationRail, NavigationRailItem with the 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);` to make this demo:
```svelte
{modal ? "Modal" : "Normal"}
{iconType == "left" ? "Icon and text" : "Icon"}
{alignment == "top" ? "Top" : "Center"}
{collapse == "normal" ? "Collapse" : collapse == "full" ? "Fully collapse" : "Don't collapse"}
{#snippet demo()}
{#snippet fab(open)}
{}}
/>
{/snippet}
{/snippet}
```
### UI transitions
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses Button with the TS `import { containerTransform, sharedAxisTransition } from "$lib/misc/animation";
let mode: "X" | "Y" | "Z" | "container" = $state("X");
let affected = $state(false);
const [send, receive] = containerTransform({ duration: 1000 });` to make this demo:
```svelte
{mode[0].toUpperCase() + mode.slice(1)}
{#snippet demo()}
{#if mode == "container"}
{#if affected}
More info now!
(affected = false)}>Close
{:else}
(affected = true)}
in:receive={{ key: "container" }}
out:send={{ key: "container" }}
>
Click
{/if}
{:else if mode == "Z"}
{#if affected}
(affected = false)}>Beta
{:else}
(affected = true)}>Alpha
{/if}
{:else if affected}
(affected = false)}>Beta
{:else}
(affected = true)}>Alpha
{/if}
{/snippet}
```
### Layer
Minimal demo:
```svelte
Hello
```
The M3 Svelte interactive demo for this component uses with the TS `` to make this demo:
```svelte
{#snippet demo()}
{/snippet}
```
### Icons
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses Icon, Slider with the TS `let size = $state(24);` to make this demo:
```svelte
n.toFixed(0) + "px"} />
{#snippet demo()}
{/snippet}
```
### Shapes
Minimal demo:
```svelte
```
The M3 Svelte interactive demo for this component uses Button with the TS `import iconGo from "@ktibow/iconset-material-symbols/arrow-forward-rounded";
import * as _paths from "$lib/misc/shapes";
import * as _pathsAnimatable from "$lib/misc/shapesAnimatable";
import * as _pathsAnimatableSmall from "$lib/misc/shapesAnimatableSmall";
import { snackbar } from "$lib/containers/Snackbar.svelte";
import ShapeSelector from "/src/routes/ShapeSelector.svelte";
const paths = _paths as Record;
const pathsAnimatable = _pathsAnimatable as Record;
const pathsAnimatableSmall = _pathsAnimatableSmall as Record;
let shape = $state("pathArch");
let mode: "normal" | "animatable" | "animatable small" = $state("normal");` to make this demo:
```svelte
{mode[0].toUpperCase() + mode.slice(1)} paths
{#snippet demo()}
{@const d =
mode == "animatable small"
? pathsAnimatableSmall[shape.replace("path", "pathAnimatableSmall")]
: mode == "animatable"
? pathsAnimatable[shape.replace("path", "pathAnimatable")]
: paths[shape]}
{#if d}
{:else}
this shape-settings combination is unavailable
{/if}
{/snippet}
```