M3Nerd
GitHub

Components

Live previews rendered with the real library. Every section shows props and a copy-paste snippet. Use the adaptive playground at the bottom to flip density and TV focus mode.

Buttons

Five variants, five sizes, icon buttons, FABs, and connected segments.

Selected: Day
ComponentPropDefaultDescription
Button'filled' | 'tonal' | 'elevated' | 'outlined' | 'text''filled'Variant sets color and elevation emphasis
Button'xs' | 's' | 'm' | 'l' | 'xl''s'Size — Expressive heights 32 / 40 / 56 / 96 / 136px
Buttonhref?: stringundefinedRenders an anchor instead of a button
IconButton'standard' | 'filled' | 'tonal' | 'outlined''standard'Icon-only button; label sets aria-label
IconButtontoggle?: boolean, selected?: booleanfalseToggle semantics with aria-pressed
FABextended?: boolean, plain?: booleanfalseExtended shows a label; plain drops elevation
FAB's' | 'm' | 'l''m'FAB size — 40, 56, or 96px
ConnectedButtonsconnected?: boolean, label?: stringtrueJoins child buttons into a segmented group
Code
<Button variant="filled" size="m">Filled</Button>
<Button variant="tonal">Tonal</Button>
<Button href="/docs/" variant="outlined">Docs</Button>

<IconButton label="Share" variant="filled">
	<Icon name="share" />
</IconButton>

<FAB extended label="Compose">
	<Icon name="edit" />Compose
</FAB>

<ConnectedButtons label="View mode">
	<Button variant="tonal" size="s">Day</Button>
	<Button variant="tonal" size="s">Week</Button>
</ConnectedButtons>

Cards & List

Surface containers for content, plus two-line list items.

elevated Elevated

Rests on the surface with a subtle shadow.

format_color_fill Filled

Primary container tint, no shadow.

palette Tonal

Secondary container for supporting content.

crop_square Outlined

Quietest option for dense layouts.

Two-line list item Supporting text wraps under the headline and stays readable at every breakpoint.
Second item ListItem keeps 56px rows by default, 48px in compact density.
ComponentPropDefaultDescription
Card'elevated' | 'filled' | 'outlined' | 'tonal''elevated'Surface treatment
Cardonclick?, href?, as?undefinedMakes the card a button or link target
ListItemheadline: stringrequiredPrimary line of text
ListItemsupporting?: stringundefinedSecondary line — renders two-line rows
Code
<Card variant="elevated" onclick={open}>
	<h3>Elevated</h3>
	<p>Rests on the surface with a subtle shadow.</p>
</Card>

<Card variant="outlined">
	<ListItem
		headline="Two-line list item"
		supporting="Secondary text stays readable at every breakpoint."
	/>
</Card>

Forms

Inputs, selection controls, chips, and progress indicators.

TextField outlined
We never share it.
TextField filled
TextField error
Only letters and numbers.
Multiline
Checkbox & Switch
Radio group
Slider — 40
Select
Chip
Svelte
DateField
CircularProgress determinate
CircularProgress indeterminate
LinearProgress
LoadingIndicator
ComponentPropDefaultDescription
TextFieldvariant?: outlined | filled'outlined'Outline style; invalid shows error color
TextFieldbind:value: string''Bound text; supportingText shows helper line
Checkboxbind:checked: boolean, label?: stringfalseCustom box with animated check
Radiovalue: string, bind:checked: booleanrequiredPair with name for native group semantics
Switchbind:checked: boolean, label?: stringfalserole="switch" track control
Sliderbind:value, min, max, step0 / 100 / 1Continuous numeric input
Selectoptions: { value, label }[], bind:value: string[]Dropdown listbox with anchored popup
Chipvariant?: assist | filter | input | suggested'assist'Chip role; label provides the text
Chipbind:selected?: booleanfalseFilter chips toggle selected state
CircularProgressvalue?: number, min?, max?undefined / 0 / 1Determinate within min–max; omitted = indeterminate
LinearProgressvalue?: number, min?, max?undefined / 0 / 1Determinate within min–max; omitted = indeterminate
DateFieldbind:value: string''ISO date text with docked picker; invalid marks errors
Code
<TextField label="Email" bind:value={email} supportingText="We never share it." />
<TextField label="Username" value="a b" invalid supportingText="Only letters and numbers." />

<Checkbox bind:checked={agree} label="Accept terms" />
<Radio name="drink" value="filter" label="Filter" checked={drink === 'filter'}
	onchange={() => (drink = 'filter')} />
<Switch bind:checked={soundOn} label="Sound" />

<Slider bind:value={volume} min={0} max={100} />
<Select label="Topic" options={[{ value: 'motion', label: 'Motion' }]} bind:value={topic} />
<Chip variant="filter" label="Wi-Fi" bind:selected={filterChip} />
<Chip variant="input" label="Svelte" onRemove={removeChip} />
<DateField bind:value={dueDate} aria-label="Due date" />

<CircularProgress value={64} max={100} />
<CircularProgress />
<LinearProgress value={64} max={100} />
<LoadingIndicator />

Overlays

Dialog, menu, sheets, and the imperative snackbar.

Discard draft?

Your changes will be lost. This cannot be undone.

Share via

ComponentPropDefaultDescription
Dialogbind:open: boolean, title?: stringfalseModal dialog with scrim and actions snippet
Menubind:open: boolean, anchorEl?: HTMLElementfalsePopup surface positioned against its trigger
MenuItemchildren, onclick?, onselect?, selected?undefinedRow inside a menu; closes the menu on click
BottomSheetbind:open: boolean, title?: stringfalseModal sheet sliding from the bottom edge
SideSheetbind:open: boolean, title?: string, side?, modal?false / undefined / 'end' / falseSupplementary panel from the inline edge
snackbar()(text, { action?, duration? }) => void4000msImperative toast; mount <Snackbar /> once
Code
<script lang="ts">
	import { Button, Dialog, Menu, MenuItem, Snackbar, snackbar } from 'm3nerd';

	let open = $state(false);
	let menuOpen = $state(false);
	let anchor = $state<HTMLSpanElement | null>(null);
</script>

<Button onclick={() => (open = true)}>Open dialog</Button>

<Dialog bind:open title="Discard draft?">
	<p>Your changes will be lost.</p>
	{#snippet actions()}
		<Button variant="text" onclick={() => (open = false)}>Cancel</Button>
		<Button variant="filled" onclick={() => (open = false)}>Discard</Button>
	{/snippet}
</Dialog>

<span bind:this={anchor}>
	<Button onclick={() => (menuOpen = true)}>Open menu</Button>
</span>
<Menu bind:open={menuOpen} anchorEl={anchor}>
	<MenuItem>Cut</MenuItem>
	<MenuItem>Copy</MenuItem>
</Menu>

<Snackbar />
<button onclick={() => snackbar('Saved')}>Save</button>

Feedback

Badges call attention to icons and avatars.

mail notifications campaign
ComponentPropDefaultDescription
Badgevalue?: string | number, max?: numberundefined / 99Badge count; values over max render as max+
Badgedot?: boolean, label?: stringfalseDot variant; label names it for assistive tech
BadgechildrenrequiredWraps the icon or avatar being badged
Code
<Badge value={3}>
	<span class="avatar">A</span>
</Badge>

<Badge value={120} max={99}>
	<Icon name="notifications" size={28} />
</Badge>

<Badge dot label="Unread items">
	<Icon name="campaign" size={28} />
</Badge>

Adaptive playground

Flip the document-level attributes and watch tokens respond. The ruler tracks the live viewport width and its breakpoint.

data-density
data-focus-mode
Token sample
0px compact
60084012001600
  • compact < 600px
  • medium 600–839px
  • expanded 840–1199px
  • large 1200–1599px
  • xlarge ≥ 1600px
ComponentPropDefaultDescription
data-density'compact' | 'spacious'default scaleSets --m3-density-scale on <html>
data-focus-mode'tv'unset64px targets, larger type, glowing focus ring
window widthmatchMedia breakpointslivecompact | medium | expanded | large | xlarge
Code
<script lang="ts">
	$effect(() => {
		const root = document.documentElement;
		if (density === 'compact') root.dataset.density = 'compact';
		else delete root.dataset.density;
	});

	$effect(() => {
		const update = () => (width = window.innerWidth);
		update();
		const queries = [600, 840, 1200, 1600].map((w) => {
			const mq = matchMedia('(min-width: ' + w + 'px)');
			mq.addEventListener('change', update);
			return mq;
		});
		window.addEventListener('resize', update);
		return () => {
			for (const mq of queries) mq.removeEventListener('change', update);
			window.removeEventListener('resize', update);
		};
	});
</script>