@noxlovette/material 0.4.2 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Danila Volkov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1 +1,88 @@
1
- # Google's Material Design System in Svelte
1
+ # @noxlovette/material
2
+
3
+ **Google's [Material Design 3](https://m3.material.io/) system, built for Svelte.**
4
+
5
+ Every component is a real MD3 citizen — the token vocabulary (color roles, typescale,
6
+ elevation, shape, motion), the state layers, the canonical layouts — implemented on top of
7
+ [Bits UI](https://bits-ui.com/)'s headless primitives and styled with
8
+ [`tailwind-variants`](https://www.tailwind-variants.org/). You get the accessibility and
9
+ interaction behavior Bits UI already gets right, with Material's actual look and feel on top.
10
+
11
+ [![npm version](https://img.shields.io/npm/v/%40noxlovette%2Fmaterial.svg)](https://www.npmjs.com/package/@noxlovette/material)
12
+ [![npm downloads](https://img.shields.io/npm/dm/%40noxlovette%2Fmaterial.svg)](https://www.npmjs.com/package/@noxlovette/material)
13
+ [![CI](https://github.com/noxlovette/material/actions/workflows/ci.yml/badge.svg)](https://github.com/noxlovette/material/actions/workflows/ci.yml)
14
+ [![license: MIT](https://img.shields.io/npm/l/%40noxlovette%2Fmaterial.svg)](./LICENSE)
15
+
16
+ **[📖 Docs & overview](https://material.noxlovette.com)** · **[🧩 Storybook — browse every component live](https://material.noxlovette.com/storybook/)**
17
+
18
+ Storybook is the canonical place to actually look at things: every component, every variant,
19
+ with Controls to poke at props and an a11y panel to check contrast and roles as you go. The
20
+ docs site is prose — install steps, theming, rationale — and links out to Storybook rather
21
+ than re-implementing a gallery of its own.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ npm i @noxlovette/material
27
+ ```
28
+
29
+ ```css
30
+ /* your root stylesheet */
31
+ @import 'tailwindcss';
32
+ @import '@noxlovette/material/styles';
33
+ @import '@noxlovette/material/theme/light';
34
+ @import '@noxlovette/material/theme/dark';
35
+ ```
36
+
37
+ ```svelte
38
+ <script lang="ts">
39
+ import { App, Button, Card, Title } from '@noxlovette/material';
40
+ </script>
41
+
42
+ <App>
43
+ <Card class="p-4">
44
+ <Title>Hello world</Title>
45
+ <Button>Click me</Button>
46
+ </Card>
47
+ </App>
48
+ ```
49
+
50
+ `App` wires up theming, dark-mode detection, and icon loading. Dynamic theming (generate a
51
+ whole MD3 theme from a source color or an image, toggle light/dark/contrast at runtime) is
52
+ covered on the [docs site](https://material.noxlovette.com), along with the SSR setup that
53
+ avoids a flash of unstyled content.
54
+
55
+ ## What's inside
56
+
57
+ Buttons, cards, dialogs, navigation rail/bar, side sheets, snackbars, tabs, sliders, chips,
58
+ menus, text fields, and the rest of the MD3 component set, plus `Pane`/`PaneGrid` for
59
+ Material's [canonical layouts](https://m3.material.io/foundations/layout/canonical-layouts)
60
+ (list-detail, supporting-pane, feed) without hand-rolling flex/sticky positioning yourself.
61
+ The full list, with live previews, is in [Storybook](https://material.noxlovette.com/storybook/).
62
+
63
+ A Claude Code skill ships alongside the library — `npx @noxlovette/material material-claude-skill`
64
+ installs it into `.claude/skills/material-design`, so an agent working in your app gets the
65
+ same token vocabulary and variant-selection rules this library was built with.
66
+
67
+ ## Credits
68
+
69
+ - [**Material Design 3**](https://m3.material.io/) — the design system itself. All token
70
+ names, color roles, and layout patterns here are Google's, not reinvented.
71
+ - [**Bits UI**](https://bits-ui.com/) — the headless primitives underneath every interactive
72
+ component. This library owes its accessibility behavior and interaction correctness to it;
73
+ ours is the Material skin on top.
74
+
75
+ ## Contributing
76
+
77
+ ```bash
78
+ bun run dev # showcase site: landing page + prose docs
79
+ bun run storybook # component workbench — the canonical way to view/QA components
80
+ bun run check # svelte-check + tsc
81
+ bun run test # vitest
82
+ ```
83
+
84
+ See [`CLAUDE.md`](./CLAUDE.md) for the fuller architecture rundown and known pitfalls.
85
+
86
+ ## License
87
+
88
+ [MIT](./LICENSE)
@@ -61,7 +61,7 @@ while keeping the main screen content visible.
61
61
  />
62
62
 
63
63
  <dialog
64
- class="bg-md-sys-color-surface-container-low mx-auto mt-auto w-full max-w-2xl overflow-hidden rounded-t-md"
64
+ class="bg-md-sys-color-surface-container-low text-md-sys-color-on-surface mx-auto mt-auto w-full max-w-2xl overflow-hidden rounded-t-md"
65
65
  style:max-height="{height}px"
66
66
  use:open
67
67
  use:outroClass
@@ -34,7 +34,7 @@ rendering the component ({#if open}<SideSheet ...>{/if}).
34
34
  </script>
35
35
 
36
36
  <dialog
37
- class="bg-md-sys-color-surface-container-low fixed inset-y-0 right-0 left-auto m-0 h-full w-full max-w-sm rounded-l-md"
37
+ class="bg-md-sys-color-surface-container-low text-md-sys-color-on-surface fixed inset-y-0 right-0 left-auto m-0 h-full w-full max-w-sm rounded-l-md"
38
38
  use:open
39
39
  use:outroClass
40
40
  oncancel={(e) => {
@@ -21,6 +21,8 @@ comment.
21
21
  children,
22
22
  title,
23
23
  subtitle,
24
+ titleProps,
25
+ subtitleProps,
24
26
  trailing,
25
27
  leading,
26
28
  class: className,
@@ -74,11 +76,13 @@ comment.
74
76
  {@render leading?.()}
75
77
  </div>
76
78
  <div class={textContainer()}>
77
- <h1 class={titleCLs()}>
79
+ <h1 class={titleCLs({ class: clsx(titleProps?.class) })} {...titleProps}>
78
80
  {title}
79
81
  </h1>
80
82
  {#if subtitle}
81
- <p class={subtitleCls()}>{subtitle}</p>
83
+ <p class={subtitleCls({ class: clsx(subtitleProps?.class) })} {...subtitleProps}>
84
+ {subtitle}
85
+ </p>
82
86
  {/if}
83
87
  </div>
84
88
  <div class={trailingCls()}>
@@ -11,6 +11,10 @@ export type AppBarProps = AppbarVariants & HTMLAttributes<HTMLElementTagNameMap[
11
11
  title: string;
12
12
  /** An optional subtitle displayed below the title. */
13
13
  subtitle?: string;
14
+ /** Additional props for the title `<h1>` element (e.g. `data-cy`). */
15
+ titleProps?: HTMLAttributes<HTMLHeadingElement>;
16
+ /** Additional props for the subtitle `<p>` element (e.g. `data-cy`). */
17
+ subtitleProps?: HTMLAttributes<HTMLParagraphElement>;
14
18
  /** A snippet to be rendered on the left side (e.g., navigation icon). */
15
19
  leading?: Snippet;
16
20
  /** A snippet to be rendered on the right side (e.g., action icons). */
@@ -0,0 +1,6 @@
1
+ type $$ComponentProps = {
2
+ generateCSS?: boolean;
3
+ };
4
+ declare const Theme: import("svelte").Component<$$ComponentProps, {}, "">;
5
+ type Theme = ReturnType<typeof Theme>;
6
+ export default Theme;
package/package.json CHANGED
@@ -1,13 +1,30 @@
1
1
  {
2
2
  "name": "@noxlovette/material",
3
- "version": "0.4.2",
3
+ "version": "0.4.7",
4
4
  "type": "module",
5
+ "description": "A Material Design 3 component library for Svelte, built on Bits UI.",
6
+ "keywords": [
7
+ "svelte",
8
+ "sveltekit",
9
+ "material-design",
10
+ "material-you",
11
+ "md3",
12
+ "component-library",
13
+ "bits-ui",
14
+ "ui"
15
+ ],
5
16
  "license": "MIT",
17
+ "homepage": "https://material.noxlovette.com",
6
18
  "repository": {
7
- "url": "git+https://codeberg.org/noxlovette/material.git"
19
+ "type": "git",
20
+ "url": "git+https://github.com/noxlovette/material.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/noxlovette/material/issues"
8
24
  },
9
25
  "scripts": {
10
26
  "build": "svelte-package",
27
+ "build:site": "vite build",
11
28
  "watch": "svelte-package --watch",
12
29
  "prepublishOnly": "bun run build",
13
30
  "check": "svelte-kit sync && svelte-check",
@@ -16,6 +33,7 @@
16
33
  "dev": "vite dev",
17
34
  "storybook": "storybook dev -p 6006",
18
35
  "build-storybook": "storybook build",
36
+ "publint": "publint",
19
37
  "index": "bun run scripts/generate-components-index.ts"
20
38
  },
21
39
  "files": [
@@ -1,65 +0,0 @@
1
- ---
2
- background: false
3
- description: Grounds UI/UX decisions in Material Design 3 (m3.material.io) and this repo's actual token/component system when creating or editing components in src/lib/components/, choosing a color role/variant/emphasis level, adding a transition or animation, laying out a showcase or docs page, or reviewing existing UI for M3 compliance. Does NOT apply to pure logic/state changes with no visual surface. For Cypress test authoring use cypress-author/cypress-explain/cypress-docs instead.
4
- model: inherit
5
- name: material-design
6
- ---
7
-
8
- # Material Design 3 for @noxlovette/material
9
-
10
- ## Purpose
11
-
12
- This repo is a single, already-opinionated M3 component library, not a blank canvas. Every visual decision (color, type, elevation, shape, motion) already has a token or utility class for it. The job here is never "invent a look" — it's "find the right existing primitive and apply it correctly." This skill encodes that mapping so you don't re-derive M3 from general knowledge or, worse, hardcode a hex/px/ms value that already has a token.
13
-
14
- ## When to use
15
-
16
- - Creating or editing a component under `src/lib/components/`
17
- - Deciding which variant (filled/tonal/outlined/text/elevated) or color role (primary/secondary/tertiary/error) fits a given action or emphasis level
18
- - Adding a transition, page transition, or list/expand animation
19
- - Building a showcase route or docs page for a component — these MUST be built from
20
- `@noxlovette/material` components (`SinglePane`/`SplitPane`/`SupportingPane` for layout), never
21
- hand-rolled flex/aside divs; see `references/component-patterns.md` for the pane decision table
22
- - Reviewing UI for M3 correctness (contrast, touch targets, focus, motion)
23
-
24
- **Skip this skill** for changes with no visual surface (pure logic, data layer, config), and for Cypress test work (use the `cypress-*` skills instead).
25
-
26
- ## Reasoning flow
27
-
28
- 1. **Map the ask to a category.** This repo groups components by M3 concept, not by page: `buttons/`, `forms/` (textfield, select, checkbox, switch, slider, radio-group, search, command, pin, tooltip), `containers/` (dialogue, side-sheet, bottom-sheet, popover, menu, list, panes, stack, scroll-area), `nav/` (appbar, navbar, rail, tabs), `cards/`, `badge/`, `pill/`, `snackbar/`, `progress/`, `table/`, `date/`, `time/`, `typography/`. Check the target category's existing `theme.ts` before writing a new one — the pattern you need probably already exists one file over.
29
-
30
- 2. **Pick emphasis → variant + color role.** See the decision table below and `references/component-patterns.md` for the full reasoning and a worked example (`buttons/theme.ts`).
31
-
32
- 3. **Only use existing tokens.** Never hardcode a color, shadow, radius, or duration — every one of those has a utility class already. Full inventory in `references/tokens-and-styles.md`.
33
-
34
- 4. **Pick a motion primitive** from `src/lib/animation/` and a duration/easing pair from `motion.css`. Decision rules in `references/motion-guide.md`.
35
-
36
- 5. **Run the pre-delivery checklist** in `references/accessibility-checklist.md` before calling the work done.
37
-
38
- ## Color-role decision table
39
-
40
- | Emphasis | Variant | Color role | Example use |
41
- | ----------------------------------------------------------- | ---------- | -------------------------------------- | --------------------------------------------- |
42
- | Highest — one primary action per screen | `filled` | `primary` (or `error` for destructive) | "Save", "Submit", "Delete account" |
43
- | High but not _the_ action | `filled` | `secondary` / `tertiary` | secondary CTA next to a filled-primary |
44
- | Medium, wants to look "chosen"/selected | `tonal` | matches the concept's color | selected chip, toggle-on state, secondary FAB |
45
- | Medium, needs a visible boundary but low fill | `outlined` | `primary`/matches concept | "Cancel" next to a filled "Confirm" |
46
- | Low, inline/repeated actions | `text` | `primary` (default) | list-item trailing action, dialog "Cancel" |
47
- | Needs to visually separate from a colored surface behind it | `elevated` | matches concept | FAB or card floating over a tonal surface |
48
-
49
- `color: default` in this codebase's `tv()` variants resolves to the same classes as `primary` — pass `primary` explicitly when in doubt.
50
-
51
- ## Quick reference
52
-
53
- - Color tokens: `md-sys-color-{primary,secondary,tertiary,error}`, each with a `-container` tonal pair and `on-*` text/icon pair (guarantees AA contrast by construction).
54
- - Type: `md-sys-typescale-{display,headline,title,body,label}-{large,medium,small}`.
55
- - Elevation: `shadow-elevation-{0..5}`.
56
- - Shape: `radius-{none,xs,sm,md,lg,xl,full}` (buttons instead drive shape via the `--btn-shape`/`--btn-pressed-shape` CSS vars in `.md-btn-morph`).
57
- - Motion durations/easings: `--md-sys-motion-duration{,-fast,-slow}` (standard, non-spatial) and `-fast-spatial/-spatial/-slow-spatial` (expressive, for things that move/resize), plus the M3 emphasized easing curve.
58
- - Shared primitives: wrap interactive surfaces with `Layer.svelte` (state layer + ripple, already skips itself under `prefers-reduced-motion`), render icons with `Icon.svelte`, use `Divider.svelte` instead of Bits UI's `Separator`.
59
-
60
- ## References
61
-
62
- - [`references/tokens-and-styles.md`](references/tokens-and-styles.md) — full token/utility inventory
63
- - [`references/component-patterns.md`](references/component-patterns.md) — `tv()` slots/variants/compoundVariants pattern + variant decision tree
64
- - [`references/motion-guide.md`](references/motion-guide.md) — which animation primitive and timing pair to reach for
65
- - [`references/accessibility-checklist.md`](references/accessibility-checklist.md) — pre-delivery checklist
@@ -1,28 +0,0 @@
1
- # Pre-delivery checklist
2
-
3
- Run through this before calling a component/UI change done.
4
-
5
- ## Contrast
6
-
7
- - Text/icon color on a colored surface should always be the matching `on-*` token (`on-primary` on `primary`, `on-primary-container` on `primary-container`, etc.), never a manually chosen color — the `on-*` tokens are generated to meet contrast requirements per theme (including the `-hc`/`-mc` high/medium-contrast variants). A manual override bypasses that guarantee.
8
- - If a design calls for a color combination with no existing `on-*` pair, that's a sign it doesn't map to an M3 role cleanly — reconsider the role choice before improvising a color.
9
-
10
- ## Touch targets
11
-
12
- - Interactive elements should hit the M3 minimum 48dp target. The existing `size` scales already encode this (e.g. button `sm`/`md` = `h-10`/`h-14`); when adding a new interactive size below that, either pad the hit area (see `.text-link`'s `min-height: 44px` + padding trick) or confirm it's genuinely dense-UI/desktop-only and document why.
13
-
14
- ## Focus
15
-
16
- - Anything focusable needs a visible focus state — use `md-sys-state-focus-indicator`, don't rely on the browser default or remove `outline` without replacing it.
17
- - Verify keyboard operability, not just click handlers — Bits UI primitives (dialogs, menus, selects) handle this by default; if you're building something interactive from scratch, check it against Bits UI's docs (https://bits-ui.com/llms.txt) for the expected keyboard model first.
18
-
19
- ## Motion
20
-
21
- - `prefers-reduced-motion`: decorative-only motion should be gated (see `references/motion-guide.md`); motion that carries meaning (e.g. confirming a drag-drop) can stay.
22
- - Don't introduce a new hand-tuned duration/easing when an existing token fits — inconsistent timing across components is one of the more noticeable ways an M3 implementation starts to feel "off."
23
-
24
- ## Consistency
25
-
26
- - No hardcoded hex/rgb colors, px shadows, px border-radius, or ms durations in new component code — every one of those should trace back to a token or utility from `references/tokens-and-styles.md`.
27
- - New multi-element components use `tv()` with `slots`, not string concatenation or `clsx` with inline conditionals.
28
- - Icons via `Icon.svelte`, dividers via `Divider.svelte`, state layers via `Layer.svelte` — grep for a raw `material-symbols-` class, a hand-rolled `<hr>`/border-based divider, or manual hover/press opacity as signs one of these was skipped.
@@ -1,52 +0,0 @@
1
- # Component patterns
2
-
3
- ## The `tv()` shape
4
-
5
- Every component's styles live in the category's `theme.ts`, built with `tv()` from `tailwind-variants`:
6
-
7
- - `slots` — one key per rendered element (`base`, `icon`, `label`, ...) so multi-element components can vary each part independently
8
- - `variants` — one axis per meaningful design decision (`variant`, `color`, `size`, `shape`, `selected`, ...); each value maps to either a class string (single-slot components) or an object keyed by slot (multi-slot components)
9
- - `compoundVariants` — combinations that need a class not expressible as a union of independent axes (almost everything about color composition lives here, since `variant` × `color` isn't a clean cross-product of independent classes)
10
-
11
- Worked example: `src/lib/components/buttons/theme.ts`. The `button` export has slots `base`/`icon`; variants `variant` (elevated/filled/tonal/outlined/text/bare), `color` (default/primary/secondary/tertiary/error), `usage` (selection/default), `size` (xs/sm/md/lg/xl), `shape` (round/square), `selected` (true/false); and ~25 `compoundVariants` entries, one per `variant`×`color` pair, each pointing at the pre-built `md-component-button-*` class from `component.css`. Match this structure exactly for new component families — don't hand-roll `clsx` strings or inline conditional classes.
12
-
13
- When adding a `size` axis, follow the existing scale's _shape_, not just its numbers: each size variant sets height, gap, padding, typescale, and (for shape-driving components) the shape CSS vars together, in one slot-scoped string — not spread across multiple variant axes that a consumer could combine incorrectly.
14
-
15
- ## Variant selection tree
16
-
17
- Ask, in order:
18
-
19
- 1. **Is this the single highest-priority action on the screen/section?** → `filled`, `color: primary` (or `error` if destructive).
20
- 2. **Is it a secondary but still prominent action, or a persistent selected/toggled state?** → `tonal`.
21
- 3. **Does it need a visible boundary but shouldn't compete visually with a filled sibling?** → `outlined`.
22
- 4. **Is it a low-emphasis, often-repeated action** (list rows, inline links, dialog dismiss)? → `text`.
23
- 5. **Does it need to visually float above a surface that's already tonal/colored** (a FAB over a colored app bar, a card menu trigger)? → `elevated`.
24
-
25
- Only fall back to `bare` (no color/background at all) when the component supplies its own coloring downstream (e.g. an icon button whose color comes from a parent's `selected` state).
26
-
27
- ## Reuse before you build
28
-
29
- - **State layer / ripple** — wrap the interactive element with `Layer.svelte` (`src/lib/utils/Layer.svelte`) rather than writing hover/press opacity by hand. It listens for `.m3-layer` on its parent, already respects `prefers-reduced-motion` for the ripple, and its tint intensity (hover 0.08 / pressed·focus 0.12) matches the M3 state-layer spec — don't retune those numbers per component.
30
- - **Icons** — always `Icon.svelte` (`name`, `fill`, `wght`, `size` props), never a raw `<span class="material-symbols-...">` or an inline SVG for a Material Symbol.
31
- - **Dividers** — `Divider.svelte`, not Bits UI's `Separator` (per project CLAUDE.md — it already implements the same primitive).
32
- - **Focus ring** — the `md-sys-state-focus-indicator` utility, not a custom `:focus` style.
33
- - **Disabled state** — `.md-component-button-base`'s disabled handling (via `disabled:`/`aria-disabled`/`data-disabled` selectors in `component.css`), not a manually-toggled opacity class.
34
-
35
- ## Laying out a showcase or docs page (`src/routes/**`)
36
-
37
- Routes are not a blank canvas either — every page-level layout shape already has a `containers/panes`
38
- component. Reach for these instead of a hand-rolled `flex`/`aside`/`sticky` div, and if none fits,
39
- that's a sign the pattern belongs in the library, not in a one-off route file:
40
-
41
- | Shape you need | Component | Notes |
42
- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
43
- | Centered/full-width single column | `SinglePane` | `centered` variant for max-width, `padding`/`gap` for spacing |
44
- | User-resizable two-column split | `SplitPane` (`resizable` default) | Drag handle + `localStorage` persistence |
45
- | Static, non-draggable sidebar fixed to the true viewport edge | `SplitPane` `anchor="viewport" resizable={false}` | Only correct if this is the outermost positioned element (nothing else is already offsetting content) |
46
- | Static sidebar absolutely positioned in a bounded/contained box | `SplitPane` `anchor="parent" resizable={false}` | Does not stay visible while the page scrolls — for embedded demos, not full-page nav |
47
- | Static sidebar nested inside other already-offset content, that should stay visible while scrolling | `SplitPane` `anchor="sticky" resizable={false}` | e.g. the docs secondary component-list nav, nested inside the root layout's Rail-offset column |
48
- | Main content + fixed-width side panel (TOC, filters, details) | `SupportingPane` `anchor="parent"` | The M3 [supporting-pane](https://m3.material.io/foundations/layout/canonical-layouts/supporting-pane) pattern |
49
-
50
- ## Before exporting a new component
51
-
52
- Follow CLAUDE.md's "Adding a New Component" steps as the mechanical checklist (create `.svelte` + `types.ts`, define `theme.ts` with `tv()`, export from the category `index.ts`, run `bun scripts/generate-components-index.ts`, add a showcase route and a docs page) — this skill governs the _design_ decisions (which variant/color/motion) that should be made before or while writing that `theme.ts`, not the export mechanics themselves.
@@ -1,25 +0,0 @@
1
- # Motion guide
2
-
3
- Source: `src/lib/animation/` (barrel-exported via `src/lib/animation/index.ts`) + duration/easing tokens in `src/lib/styles/motion.css`. M3 motion principle to apply: motion should be **spatial** (things visibly move/resize/morph) when the UI's structure is changing, and **standard/non-spatial** (opacity/color only) when it's a simple state change — spatial motion on a small state change reads as sluggish; standard easing on a big layout change reads as a jump-cut.
4
-
5
- ## Picking a primitive
6
-
7
- | Situation | Primitive | Notes |
8
- | -------------------------------------------------------------------------------------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
9
- | A surface expands into another (card → dialog, thumbnail → detail view, FAB → sheet) | `containerTransform` | Crossfades + morphs bounds/radius/color between the two elements sharing a `key`. Needs two DOM nodes in a Svelte `{#key}`/list-transition context, not a single toggled element. |
10
- | Stepped/sequential navigation (wizard steps, tabs, paginated content) | `sharedAxisTransition` | Pick `direction: 'X'` for horizontal steps, `'Y'` for vertical, `'Z'` for depth (e.g. modal-like overlays that don't share geometry). Set `rightSeam`/`leaving` per the doc-comment in `sharedAxisTransition.ts` — getting the boolean backwards makes both directions look identical. |
11
- | Simple show/hide with no structural relationship between what's leaving and entering | `enterExit` | `mode` options: `fade` (default), `scale`, `slide-up`, `dialog`, `dialog-m3`. Use `dialog-m3` for actual M3 dialogs/sheets (scale-only, no vertical translate) — `dialog`/`slide-up` are for non-M3-spec popovers/toasts. |
12
- | A list item needs a CSS hook while it's animating out (e.g. to suppress hover/press styles during outro) | `outroClass` | Svelte action; adds/removes a `leaving` class on `outrostart`/`outroend`. |
13
-
14
- All three transition functions default their `easing` to `easeEmphasized` (from `easing.ts`) already — only override `easing`/`duration` when the default doesn't fit, and when you do, pull from the named exports (`easeEmphasized`, `easeEmphasizedAccel`, `easeEmphasizedDecel`, `easeStandard`, `easeStandardAccel`, `easeStandardDecel`), not a raw `cubic-bezier(...)`.
15
-
16
- ## Picking a duration when hand-rolling CSS transitions
17
-
18
- For anything not going through the animation module (e.g. a plain CSS `transition` on `theme.ts`-driven classes), use the `--md-sys-motion-easing*` custom properties from `motion.css` directly, chosen by what's animating:
19
-
20
- - **Standard** (`easing` 200ms / `easing-fast` 150ms / `easing-slow` 300ms) — color, opacity, elevation, focus ring: small/local, non-spatial changes. `.state-layer` and `.text-link` already use these.
21
- - **Expressive/spatial** (`easing-fast-spatial` 350ms / `easing-spatial` 500ms / `easing-slow-spatial` 650ms) — anything that changes position, size, or shape. Bigger/farther-traveling elements get the slower tier; small in-place morphs (like the button's press-radius morph in `.md-btn-morph`) use a fast, hand-tuned duration instead of these tiers because they're driven by direct user press feedback, not a triggered transition.
22
-
23
- ## Reduced motion
24
-
25
- `Layer.svelte`'s ripple already checks `prefers-reduced-motion` and no-ops. The `containerTransform`/`sharedAxisTransition`/`enterExit` functions do **not** self-guard — if you use them for a transition that isn't purely decorative (i.e. skipping it would leave the UI in a confusing state), that's fine as-is, but for a purely decorative flourish, gate it on `prefers-reduced-motion` at the call site rather than assuming the animation module handles it for you.
@@ -1,57 +0,0 @@
1
- # Tokens and styles inventory
2
-
3
- Source of truth: `src/lib/styles/*.css`. Rule: **never hardcode a color, shadow, radius, or duration value in a component — every one of these already has a token or utility class.** If you think you need a new value, check these files first; you're almost certainly missing an existing one.
4
-
5
- ## Color roles (`src/lib/styles/theme/*.css`)
6
-
7
- Defined per theme variant (`light.css`, `dark.css`, `light-hc.css`, `dark-hc.css`, `light-mc.css`, `dark-mc.css` — high-contrast and medium-contrast accessibility variants) as `--md-sys-color-*` custom properties, consumed via Tailwind utilities of the form `bg-md-sys-color-*` / `text-md-sys-color-*` / `outline-md-sys-color-*`.
8
-
9
- - Core roles: `primary`, `secondary`, `tertiary`, `error`
10
- - Each has a low-emphasis tonal pair: `{role}-container`
11
- - Each (including containers) has a guaranteed-contrast text/icon pair: `on-{role}`, `on-{role}-container`
12
- - Surface roles: `surface`, `surface-variant`, `surface-container` (`-lowest`, `-low`, `-high`, `-highest`), `on-surface`, `on-surface-variant`
13
- - Structural: `outline`, `outline-variant`, `shadow`
14
-
15
- Because `on-*` pairs are generated to meet contrast requirements, **any manual color override that doesn't go through an `on-*`/role pair is a contrast bug waiting to happen** — flag it in review.
16
-
17
- ## Component color composition (`src/lib/styles/component.css`)
18
-
19
- Components don't reference `md-sys-color-*` directly for every state — they compose pre-built classes:
20
-
21
- ```
22
- md-component-button-filled-{default,primary,secondary,tertiary,error}
23
- md-component-button-tonal-{default,primary,secondary,tertiary,error}
24
- md-component-button-outline-{default,primary,secondary,tertiary,error}
25
- md-component-button-text-{default,primary,secondary,tertiary,error}
26
- md-component-button-elevated-{default,primary,secondary,tertiary,error}
27
- ```
28
-
29
- `filled` = `bg-md-sys-color-{role} text-md-sys-color-on-{role}`. `tonal` = the `-container`/`on-{role}-container` pair. `outlined` = `text-md-sys-color-{role} outline-md-sys-color-{role}` (default outline uses `on-surface-variant`/`outline-variant`). `text` = text color only, transparent background. `elevated` = `bg-md-sys-color-surface-container-low` + role text color + `shadow-elevation-1`. When adding a new component family with the same emphasis levels, follow this exact composition rather than reinventing it.
30
-
31
- Other reusable utilities in this file:
32
-
33
- - `.state-layer` — the `::before` overlay hook that `Layer.svelte` and hover/press states build on
34
- - `md-sys-state-focus-indicator` — the `:focus-visible` outline (3px solid, `on-secondary` color, 2px offset) — apply to anything focusable that doesn't already get it via a shared base class
35
- - `.md-component-button-base` — disabled-state handling (`on-surface` at reduced opacity) + cursor — reuse for any new interactive component base
36
-
37
- ## Typescale (`src/lib/styles/typescale.css`)
38
-
39
- `md-sys-typescale-{display,headline,title,body,label}-{large,medium,small}`, plus a one-off `md-sys-typescale-fab-label`. Each bakes in the correct font size, line height, tracking, and (for title/label) weight — don't set `text-*`/`leading-*`/`tracking-*` manually when one of these fits.
40
-
41
- ## Elevation (`src/lib/styles/elevation.css`)
42
-
43
- `shadow-elevation-{0..5}`, each a two-layer (spot + ambient) shadow scaled to the M3 elevation spec, using `color-mix` against the `shadow` color role so it adapts per theme automatically. Elevation communicates depth/priority — reach for a higher level when a surface should read as "above" its neighbors (menus, FABs, dialogs), not as a decorative effect.
44
-
45
- ## Shape (`src/lib/styles/rounding.css`)
46
-
47
- `radius-{none,xs,sm,md,lg,xl,full}` = `0 / 4px / 8px / 12px / 16px / 28px / 9999px`, matching the M3 shape scale (extra-small through extra-large, plus full/pill). Buttons don't use these directly — they use CSS custom properties (`--btn-shape`, `--btn-shape-override`, `--btn-pressed-shape` set per `size`/`shape` variant in `theme.ts`) consumed by the `.md-btn-morph` utility, which also animates the radius on press (a deliberate M3 "morph" detail — don't remove it when adding button variants).
48
-
49
- ## Motion (`src/lib/styles/motion.css`)
50
-
51
- Two families of duration/easing pairs, exposed as `--md-sys-motion-easing*` custom properties combining a timing function + duration:
52
-
53
- - **Standard** (color/opacity/simple UI changes): `easing` (200ms), `easing-fast` (150ms), `easing-slow` (300ms)
54
- - **Expressive/spatial** (things that move, resize, or morph): `easing-fast-spatial` (350ms), `easing-spatial` (500ms), `easing-slow-spatial` (650ms)
55
- - The M3 "emphasized" curve is also available as three raw timing functions (`emphasized`, `emphasized-accel`, `emphasized-decel`) for hand-rolled transitions — see `references/motion-guide.md` for when to use these vs. the animation module.
56
-
57
- Full decision guidance for motion lives in `references/motion-guide.md` — don't just guess a duration.