pigment-mcp 0.5.0 → 0.6.0
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/cli.js +1 -1
- package/content/registry.json +7 -1
- package/content/shots.json +18 -0
- package/content/templates/margin-notes/1.1.0/CHANGELOG.md +92 -0
- package/content/templates/margin-notes/1.1.0/agent/instructions.md +44 -0
- package/content/templates/margin-notes/1.1.0/components/badge.md +9 -0
- package/content/templates/margin-notes/1.1.0/components/button.md +11 -0
- package/content/templates/margin-notes/1.1.0/components/callout.md +9 -0
- package/content/templates/margin-notes/1.1.0/components/card.md +11 -0
- package/content/templates/margin-notes/1.1.0/components/checkbox.md +21 -0
- package/content/templates/margin-notes/1.1.0/components/chip.md +9 -0
- package/content/templates/margin-notes/1.1.0/components/code-block.md +9 -0
- package/content/templates/margin-notes/1.1.0/components/dialog.md +9 -0
- package/content/templates/margin-notes/1.1.0/components/empty-state.md +9 -0
- package/content/templates/margin-notes/1.1.0/components/index.json +22 -0
- package/content/templates/margin-notes/1.1.0/components/media-frame.md +9 -0
- package/content/templates/margin-notes/1.1.0/components/navigation.md +11 -0
- package/content/templates/margin-notes/1.1.0/components/segmented-control.md +9 -0
- package/content/templates/margin-notes/1.1.0/components/select.md +9 -0
- package/content/templates/margin-notes/1.1.0/components/switch.md +9 -0
- package/content/templates/margin-notes/1.1.0/components/table.md +31 -0
- package/content/templates/margin-notes/1.1.0/components/tabs.md +9 -0
- package/content/templates/margin-notes/1.1.0/components/text-field.md +11 -0
- package/content/templates/margin-notes/1.1.0/components/toast.md +9 -0
- package/content/templates/margin-notes/1.1.0/dist/agent.md +706 -0
- package/content/templates/margin-notes/1.1.0/dist/tokens.css +301 -0
- package/content/templates/margin-notes/1.1.0/dist/tokens.json +462 -0
- package/content/templates/margin-notes/1.1.0/manifest.json +108 -0
- package/content/templates/margin-notes/1.1.0/principles/visual-language.md +227 -0
- package/content/templates/margin-notes/1.1.0/tokens/primitives.json +611 -0
- package/content/templates/margin-notes/1.1.0/tokens/semantic.dark.json +200 -0
- package/content/templates/margin-notes/1.1.0/tokens/semantic.light.json +199 -0
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -34968,7 +34968,7 @@ var init_package = __esm({
|
|
|
34968
34968
|
"package.json"() {
|
|
34969
34969
|
package_default = {
|
|
34970
34970
|
name: "@pigment/mcp",
|
|
34971
|
-
version: "0.
|
|
34971
|
+
version: "0.6.0",
|
|
34972
34972
|
private: true,
|
|
34973
34973
|
type: "module",
|
|
34974
34974
|
description: "MCP server for Pigment design systems: tokens, rules, component specs and installation guides for your coding agent.",
|
package/content/registry.json
CHANGED
|
@@ -2,13 +2,19 @@
|
|
|
2
2
|
"templates": [
|
|
3
3
|
{
|
|
4
4
|
"id": "margin-notes",
|
|
5
|
-
"latest": "1.
|
|
5
|
+
"latest": "1.1.0",
|
|
6
6
|
"versions": [
|
|
7
7
|
{
|
|
8
8
|
"version": "1.0.0",
|
|
9
9
|
"status": "published",
|
|
10
10
|
"publishedAt": "2026-10-12T14:38:41.015Z",
|
|
11
11
|
"sha256": "e67b252ca8320bac932269460990143e99a2ec25c7fee2fa029af355076e29d1"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"version": "1.1.0",
|
|
15
|
+
"status": "published",
|
|
16
|
+
"publishedAt": "2026-10-14T08:33:30.334Z",
|
|
17
|
+
"sha256": "f6a26186965b8425d7b1ea620c737f58e8e907746b1550fb32a7b4bf98b6fa04"
|
|
12
18
|
}
|
|
13
19
|
]
|
|
14
20
|
},
|
package/content/shots.json
CHANGED
|
@@ -17,6 +17,24 @@
|
|
|
17
17
|
"settings.mobileLight": "/showcase/margin-notes/1.0.0/settings/settings-profile-mobile-light.png",
|
|
18
18
|
"settings.mobileDark": "/showcase/margin-notes/1.0.0/settings/settings-profile-mobile-dark.png"
|
|
19
19
|
},
|
|
20
|
+
"margin-notes@1.1.0": {
|
|
21
|
+
"blog.desktopLight": "/showcase/margin-notes/1.1.0/blog/home-desktop-light.png",
|
|
22
|
+
"blog.desktopDark": "/showcase/margin-notes/1.1.0/blog/home-desktop-dark.png",
|
|
23
|
+
"blog.mobileLight": "/showcase/margin-notes/1.1.0/blog/home-mobile-light.png",
|
|
24
|
+
"blog.mobileDark": "/showcase/margin-notes/1.1.0/blog/home-mobile-dark.png",
|
|
25
|
+
"commerce.desktopLight": "/showcase/margin-notes/1.1.0/commerce/home-desktop-light.png",
|
|
26
|
+
"commerce.desktopDark": "/showcase/margin-notes/1.1.0/commerce/home-desktop-dark.png",
|
|
27
|
+
"commerce.mobileLight": "/showcase/margin-notes/1.1.0/commerce/home-mobile-light.png",
|
|
28
|
+
"commerce.mobileDark": "/showcase/margin-notes/1.1.0/commerce/home-mobile-dark.png",
|
|
29
|
+
"dashboard.desktopLight": "/showcase/margin-notes/1.1.0/dashboard/home-desktop-light.png",
|
|
30
|
+
"dashboard.desktopDark": "/showcase/margin-notes/1.1.0/dashboard/home-desktop-dark.png",
|
|
31
|
+
"dashboard.mobileLight": "/showcase/margin-notes/1.1.0/dashboard/home-mobile-light.png",
|
|
32
|
+
"dashboard.mobileDark": "/showcase/margin-notes/1.1.0/dashboard/home-mobile-dark.png",
|
|
33
|
+
"settings.desktopLight": "/showcase/margin-notes/1.1.0/settings/settings-profile-desktop-light.png",
|
|
34
|
+
"settings.desktopDark": "/showcase/margin-notes/1.1.0/settings/settings-profile-desktop-dark.png",
|
|
35
|
+
"settings.mobileLight": "/showcase/margin-notes/1.1.0/settings/settings-profile-mobile-light.png",
|
|
36
|
+
"settings.mobileDark": "/showcase/margin-notes/1.1.0/settings/settings-profile-mobile-dark.png"
|
|
37
|
+
},
|
|
20
38
|
"inkleaf@1.0.0": {
|
|
21
39
|
"blog.desktopLight": "/showcase/inkleaf/1.0.0/blog/home-desktop-light.png",
|
|
22
40
|
"blog.desktopDark": "/showcase/inkleaf/1.0.0/blog/home-desktop-dark.png",
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
Margin Notes is now called Pigment Margin Notes, because it is the design of this site itself. The design
|
|
6
|
+
is unchanged: projects that already use it need no changes.
|
|
7
|
+
|
|
8
|
+
- Name: "Margin Notes" → "Pigment Margin Notes". The id `margin-notes`, spec links and MCP calls stay the same.
|
|
9
|
+
|
|
10
|
+
## 1.0.0
|
|
11
|
+
|
|
12
|
+
First release of Margin Notes: warm paper and ink, hairlines, a tightly tracked grotesk, and studio details
|
|
13
|
+
(watercolor bloom, tape, pencil notes, one serif word) kept to the margins, in light and "night paper" dark.
|
|
14
|
+
Ten defining rules, a Never list, 18 specified components with designed states, copyable recipes for every
|
|
15
|
+
studio detail, and agent instructions for new and existing projects.
|
|
16
|
+
|
|
17
|
+
Changes since 0.1.0-rc.3 (critic avg 3.83; one failed gate: a commerce smoke test could not click a checkbox):
|
|
18
|
+
|
|
19
|
+
- Checkbox, radio and switch: the native input is the visible control (`appearance: none`, class `check` /
|
|
20
|
+
`switch`), so it takes every click; drawn boxes and thumb spans get `pointer-events: none`. Never list:
|
|
21
|
+
a decorative layer that intercepts clicks on a native control. Verification: click each one with the mouse.
|
|
22
|
+
- Segmented control: new tokens `color.surface.selected` (dark `#38362f`, lighter than the track) and
|
|
23
|
+
`color.border.selected` (dark ring 4.6:1 on the track); the selected segment carries the ring in both themes.
|
|
24
|
+
- Hatch toned down: `color.decor.hatch` light `#d4cfc4` (1.25:1 on the tray), dark `#3f3d37` (1.34:1), 10px
|
|
25
|
+
pitch, stated range 1.15:1 to 1.35:1; grids of 3 or more asset-less frames use 3:2.
|
|
26
|
+
- Linked-card hover: 1px `color.text.tertiary` ring plus title underline (was an invisible border step).
|
|
27
|
+
- Search fields hide the browser clear button; the system's own clear button is specified. Native selects
|
|
28
|
+
drop the default arrow. Never list: browser-default chrome.
|
|
29
|
+
- Focus rings never touch text: header links are padded `size.controlSm` boxes; inline links get padding and
|
|
30
|
+
a 3px offset.
|
|
31
|
+
- Success feedback: a toast, or a success badge with a check icon for in-place status text; never a bare
|
|
32
|
+
colored word. The toast covers every save and submit.
|
|
33
|
+
- Dialog: exact centering CSS for native and custom dialogs, mobile width, initial focus ring visible.
|
|
34
|
+
- Hierarchy: h1 repeats moved to the Never list with the "Projects / All projects" example; state markers
|
|
35
|
+
("Current plan") are neutral badges, never button-sized ink; the article table of contents is a right rail
|
|
36
|
+
or a collapsed "On this page" after the meta line, never between breadcrumb and h1; headings balance-wrap.
|
|
37
|
+
- Spacing: card grids `align-items: start`, cards span only the columns their content fills (no card more
|
|
38
|
+
than 30% empty); the mobile action bar is fixed to the viewport at full width; tables use
|
|
39
|
+
`table-layout: fixed` with column widths so filtering moves no column; inline badges get `space.8`.
|
|
40
|
+
- Mono rule: numbers inside sentences and status lines stay sans.
|
|
41
|
+
- Contradictions resolved: serif words are allowed in blog post titles but not on product, project or record
|
|
42
|
+
pages; pencil-note words must restate their target (per-app wording given; the store note no longer
|
|
43
|
+
prescribes fixed text); the settings note sits inside the sidebar (`--note-x: space.16`).
|
|
44
|
+
- Budget: shadows written with 8-digit hex, unused tokens removed (`color.action.disabled`, `color.code.ring`,
|
|
45
|
+
`font.size.display`, `font.tracking.normal`, `size.rowCompact`), prose compressed; bundle 5,993 words.
|
|
46
|
+
|
|
47
|
+
## 0.1.0-rc.3
|
|
48
|
+
|
|
49
|
+
Revision answering the critic review of rc.2 (avg 3.67). Header band sized to content, a visible hatch token,
|
|
50
|
+
stacked mobile tables that keep status and actions, pencil-note anchoring recipe, dark switch thumb and
|
|
51
|
+
lifted surface, rebuilt night bloom, lighter charts, stat cards, one container width in every app, article
|
|
52
|
+
prose spacing, empty states on the h1's axis, and an exact mono rule. Details in `pipeline/pigment-study/iterations.md`.
|
|
53
|
+
|
|
54
|
+
## 0.1.0-rc.2
|
|
55
|
+
|
|
56
|
+
Revision answering the first critic review of rc.1 (avg 3.25, polish 2.5; all automated gates passed).
|
|
57
|
+
|
|
58
|
+
- Bloom: new recipe in a full-bleed band (`overflow-x: clip`, never `overflow: hidden` on an ancestor), a feathered
|
|
59
|
+
radial `mask-image` so no straight edge can appear, a 500 x 320 box inside a 320px-min band, and a separate
|
|
60
|
+
geometry below 1200px (176 x 112, behind the h1 only, ending above the lead). Empty-state bloom placement.
|
|
61
|
+
- Dark bloom: new `mn-wash-night` filter (blurred, no grain or rim) switched by `prefers-color-scheme` and
|
|
62
|
+
`[data-theme]`; dark `opacity.bloom` 0.22 → 0.3.
|
|
63
|
+
- Serif word: never a whole title, only in titles of 2 or more words, max 1 per screen, per-app list (none in
|
|
64
|
+
settings, forms, cart, checkout, product and record pages). Added to Never.
|
|
65
|
+
- Mobile: no horizontal scroll at 390px (`overflow-x: clip` on root, `min-width: 0`), navs with more than 3 links
|
|
66
|
+
collapse to a menu button, tables scroll in their own wrapper (up to 4 columns) or stack (5 or more), sticky-bar
|
|
67
|
+
pages get bottom padding.
|
|
68
|
+
- Controls: explicit input, select and search heights that never shrink in flex rows (rule 8); switch off-track and
|
|
69
|
+
checkbox borders use `color.text.tertiary` for 3:1.
|
|
70
|
+
- Media frame "no asset" state (tray, hatch, one 20px outline icon); illustrated placeholders added to Never.
|
|
71
|
+
- Object pages use the 44px `h1Article` size; no h2 repeating the h1; status badge next to the title.
|
|
72
|
+
- Mono rule 7 made explicit (tables, spec rows, key/value rows, totals; prices and stat values stay sans).
|
|
73
|
+
- Pencil notes: one concrete placement per app type.
|
|
74
|
+
- States: hover, focus-visible, disabled and error moved into rule 9 and the Never list; Verification gains
|
|
75
|
+
code-checkable rules for agents that cannot render.
|
|
76
|
+
- Cards in mixed rows align to the top; featured cards span grid columns; product name as text, no icon.
|
|
77
|
+
- Trimmed for the 6,000-word budget: component Do/Don't lines folded into states or the Never list, swash filter
|
|
78
|
+
without rim, removed the unavailable badge variant, the alternate pencil arrow and the second tape clip-path.
|
|
79
|
+
|
|
80
|
+
## 0.1.0-rc.1
|
|
81
|
+
|
|
82
|
+
First release candidate of Margin Notes, derived from the approved reference analysis (captured 2026-10-12; see `provenance` in the manifest).
|
|
83
|
+
|
|
84
|
+
- Tokens: warm paper neutrals, ink action and focus, deep status colors including a derived ochre warning and
|
|
85
|
+
derived status tints, decor colors for the watercolor bloom and tape, code colors, radii 5 to 20 plus pill,
|
|
86
|
+
a pixel-named spacing scale, a type scale with size-dependent tracking, weights 400/500/600, rest, pinned and
|
|
87
|
+
segment elevation, motion 120/180ms on `cubic-bezier(0.2, 0, 0, 1)`, breakpoints 860/1200/1440.
|
|
88
|
+
- Dark mode "night paper" is derived (the reference is light only): action and focus invert to paper.
|
|
89
|
+
- Principles: the ten defining rules, a Never list, and "Studio details in a product" with placement limits,
|
|
90
|
+
per-app guidance and copyable recipes for the bloom, tape, pencil note, serif word and swash.
|
|
91
|
+
- Components: the 12 required components specified with derived disabled, loading and error states, plus
|
|
92
|
+
segmented control, chip, code block, callout, empty state and media frame.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
## Setup notes for this system
|
|
2
|
+
|
|
3
|
+
- **Fonts** (OFL): Geist 400/500/600, Geist Mono 400/500, Instrument Serif 400 italic, Caveat 400; `font-display: swap`.
|
|
4
|
+
- **Theme:** dark follows `prefers-color-scheme` unless `[data-theme]` overrides it, both handled by `tokens.css`; a toggle only sets `data-theme`.
|
|
5
|
+
- **Root:** the filter SVG once; `html, body { overflow-x: clip }`; global `:focus-visible`. Body `color.surface.default`, `color.text.body`, `font.size.body`, `font.lineHeight.ui`, `font.family.sans`.
|
|
6
|
+
|
|
7
|
+
## New project
|
|
8
|
+
|
|
9
|
+
1. Inspect the stack; propose an installation plan and wait for approval.
|
|
10
|
+
2. Copy `dist/tokens.css` into global styles (or map `dist/tokens.json`); never retype values.
|
|
11
|
+
3. Set up the root; build components with every state, then the studio details.
|
|
12
|
+
4. Create `design-system/` with `VERSION` (`margin-notes@1.0.0`), the tokens and `components.md`.
|
|
13
|
+
5. Add to `AGENTS.md` and `CLAUDE.md` (create if missing): "UI work follows design-system/ — read it before building screens."
|
|
14
|
+
|
|
15
|
+
## Existing project
|
|
16
|
+
|
|
17
|
+
1. Audit styling, shared components, routes and tests.
|
|
18
|
+
2. Propose a migration plan and wait for approval.
|
|
19
|
+
3. Map the tokens onto existing variables, aliasing old names; a brand action color becomes `color.action.primary`.
|
|
20
|
+
4. Restyle one representative screen and wait for review; then go screen by screen, one commit each.
|
|
21
|
+
5. Preserve routes, business logic, API contracts, form fields and validation, test ids, text used by tests, accessibility attributes. Restyle existing markup; add classes, never replace elements.
|
|
22
|
+
6. Finish with `design-system/` and the pointer lines from New project.
|
|
23
|
+
|
|
24
|
+
## Ongoing development
|
|
25
|
+
|
|
26
|
+
- Read `design-system/` first; use semantic tokens and existing components, never hard-coded values.
|
|
27
|
+
- Uncovered needs: compose from tokens, note it in `components.md`; conflicting requests (a brand hue): present options.
|
|
28
|
+
|
|
29
|
+
## Verification
|
|
30
|
+
|
|
31
|
+
Check 1440px and 390px, light and dark: Tab focus, hover, empty and successful submits of each form, an open dialog. If you cannot render, check the code:
|
|
32
|
+
- Build and tests pass; no raw colors, sizes or radii outside the token files.
|
|
33
|
+
- Click every checkbox, radio and switch with the mouse: it toggles.
|
|
34
|
+
- 1440px: content starts at 40px; lead to first content at most `space.64`; no half-empty card; filtering moves no column.
|
|
35
|
+
- 390px: `scrollWidth` equals the viewport; statuses and row actions visible; the action bar fixed, full width.
|
|
36
|
+
- Light: hatch quieter than text. Dark: selected segment ringed and lighter than its track; off switch thumb visible; bloom on `mn-wash-night`. Both: linked-card hover visible.
|
|
37
|
+
- Dialog centered with the initial focus ring; every success shown by a toast or success badge.
|
|
38
|
+
- Walk the ten rules and the Never list on every screen; note arrows land within 12px of their target.
|
|
39
|
+
- Report files changed, what you could not apply, and decisions you made.
|
|
40
|
+
|
|
41
|
+
## Rollback
|
|
42
|
+
|
|
43
|
+
- Each screen is its own commit and can be reverted alone.
|
|
44
|
+
- Tokens, fonts and the filter SVG are one commit; reverting it last restores the previous look.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### Badge
|
|
2
|
+
|
|
3
|
+
**Use.** Non-interactive status, version, count, category or marker.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** 22px tall, padding 0 `space.8`, `font.size.caption`, `radius.full`, optional 6px dot or 12px icon at `space.6`, 1 to 2 words. After inline text: `margin-left: space.8; vertical-align: middle`.
|
|
6
|
+
|
|
7
|
+
**Variants.** Status: `color.status.{tone}Subtle` ground, `color.status.{tone}` text and dot. Neutral: `color.surface.subtle`, `color.text.secondary`; also markers (current plan, "This device") beside the card title. Count (max 1 per screen, e.g. cart): `color.action.primary`, `color.text.onAction`. Version or ID: mono, `radius.inner`, 1px `color.border.strong`. A marker rendered as a `<button>` keeps the element, styled as this badge.
|
|
8
|
+
|
|
9
|
+
**A11y.** Counts get a hidden noun ("3 unread").
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
### Button
|
|
2
|
+
|
|
3
|
+
**Use.** Secondary for most actions; inline navigation is a text link.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** Optional `size.icon` icon, gap `space.8`, `radius.control`, no shadow. Small `size.controlSm`, padding 0 `space.12`, `font.size.small`. Default `size.control`, padding 0 `space.16`, `font.size.ui`. Large `size.controlLg`, padding 0 `space.20`, `radius.controlLg`. Icon-only: square, `aria-label`.
|
|
6
|
+
|
|
7
|
+
**Variants.** Primary: `color.action.primary`, `color.text.onAction`, medium. Secondary: `color.surface.raised`, 1px `color.border.control`, `color.text.primary`. Ghost: `color.text.secondary`. Danger: `color.action.danger`, `color.text.onAction`, only to confirm a destructive action.
|
|
8
|
+
|
|
9
|
+
**States.** Hover: primary `color.action.primaryHover`, secondary border `color.border.strong`, ghost `color.text.primary` on `color.surface.subtle`, danger `color.action.dangerHover`. Active: primary `color.action.primaryActive`, secondary `color.surface.subtle`. Loading keeps the width.
|
|
10
|
+
|
|
11
|
+
**A11y.** Native `<button>`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### Callout
|
|
2
|
+
|
|
3
|
+
**Use.** Inline message: info, success, warning, danger.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** 16px icon, optional title (`font.size.ui`, semibold), body `font.size.small`, optional text action. Padding `space.14` `space.16`, gap `space.10`, `radius.callout`, ground `color.status.{tone}Subtle`, bar `inset 3px 0 0` and icon `color.status.{tone}`.
|
|
6
|
+
|
|
7
|
+
**States.** Static; dismissible ones get a ghost icon button (`aria-label="Dismiss"`).
|
|
8
|
+
|
|
9
|
+
**A11y.** Danger after an action: `role="alert"`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
### Card
|
|
2
|
+
|
|
3
|
+
**Use.** One object or widget.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** Optional mono eyebrow, title (`font.size.cardTitle` or `panelTitle`), body, meta `font.size.small` `color.text.tertiary`, actions. Padding `space.24` (`space.20` under `breakpoint.md`), gap `space.12`, `radius.panel`; sized to content (Layout adaptation).
|
|
6
|
+
|
|
7
|
+
**Variants.** Rest: `color.surface.raised`, `elevation.rest`. Pinned: `color.surface.lifted`, `elevation.pinned`, may carry tape; spans grid columns, never a fixed width. Stat: one-line eyebrow (ellipsis, full text in `title`), value sans `clamp(28px, 3vw, 40px)` semibold, `font.tracking.h1Article`, delta `font.size.small` `color.text.secondary` with a 12px arrow.
|
|
8
|
+
|
|
9
|
+
**States.** Hover (linked card): ring `inset 0 0 0 1px` `color.text.tertiary` (pinned: after `elevation.pinned`), title underlined; no lift. Focus: on the card. Error: a danger callout inside.
|
|
10
|
+
|
|
11
|
+
**A11y.** One link on the title, stretched with a pseudo-element.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
### Checkbox
|
|
2
|
+
|
|
3
|
+
**Use.** Independent options and row selection; an immediate setting uses a switch.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** The native input, with class `check`, is the visible 18px box, so it takes every click; nothing sits above it. Label `font.size.ui` at `space.10`; hit area at least `size.controlSm`; rows `space.12` apart. Existing markup with a drawn box: the input covers it (`position: absolute; inset: 0; opacity: 0; z-index: 1`), the box gets `pointer-events: none`.
|
|
6
|
+
|
|
7
|
+
```css
|
|
8
|
+
.check { appearance: none; box-sizing: border-box; margin: 0; flex: none; width: 18px; height: 18px;
|
|
9
|
+
display: inline-grid; place-content: center; border: 1px solid var(--color-text-tertiary); border-radius: var(--radius-swatch);
|
|
10
|
+
background: var(--color-surface-raised); cursor: pointer; }
|
|
11
|
+
.check[type="radio"] { border-radius: var(--radius-full); }
|
|
12
|
+
.check:is(:checked, :indeterminate) { background: var(--color-action-primary); border-color: var(--color-action-primary); }
|
|
13
|
+
.check[type="checkbox"]:checked::before { content: ""; width: 9px; height: 5px; border: solid var(--color-text-on-action);
|
|
14
|
+
border-width: 0 0 1.5px 1.5px; transform: translateY(-1px) rotate(-45deg); }
|
|
15
|
+
.check:indeterminate::before { content: ""; width: 8px; height: 1.5px; background: var(--color-text-on-action); }
|
|
16
|
+
.check[type="radio"]:checked::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--color-text-on-action); }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**States.** Hover: border `color.text.secondary`. Error: border `color.status.danger`, group message below.
|
|
20
|
+
|
|
21
|
+
**A11y.** Groups use `<fieldset>` and `<legend>`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### Chip
|
|
2
|
+
|
|
3
|
+
**Use.** Toggleable filters in a row.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** `size.controlMd`, padding 0 `space.14`, `radius.full`, `font.size.small`, `color.surface.raised`, 1px `color.border.control`; optional icon or mono count; `space.6` apart. A scrolling strip scrolls the selected chip into view on load (`scrollIntoView({ inline: "nearest" })`) and fades its edge over 24px while chips are off-screen.
|
|
6
|
+
|
|
7
|
+
**States.** Hover: border `color.border.strong`. Selected: `color.action.primary` fill, `color.text.onAction`, no border.
|
|
8
|
+
|
|
9
|
+
**A11y.** `<button aria-pressed>`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### Code block
|
|
2
|
+
|
|
3
|
+
**Use.** Code, commands, raw data. Inline code: mono `font.size.small` on `color.surface.subtle`, padding 0 `space.4`, `radius.swatch`.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** Optional header (mono caption file name `color.code.muted`, copy button). Body mono `font.size.small`, `font.lineHeight.code`, padding `space.16` `space.20`, `radius.media`, scrolls inside itself.
|
|
6
|
+
|
|
7
|
+
**Colors.** `color.code.bg`, `color.code.text`, strings `color.code.string`, comments `color.code.muted`; dark theme adds a 1px `color.border.default` ring.
|
|
8
|
+
|
|
9
|
+
**States.** Copy button reads "Copied" for 2s. Focus on the dark ground: outline in `color.code.text`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### Dialog
|
|
2
|
+
|
|
3
|
+
**Use.** Confirm a consequential action or collect short input.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** Centered on both axes at every width; never top-aligned or a bottom sheet. Native `<dialog>`: `position: fixed; inset: 0; margin: auto; height: fit-content; width: min(var(--size-dialog), 100vw - var(--space-32)); max-height: calc(100dvh - var(--space-64)); overflow: auto`, `::backdrop` `color.surface.overlay`; a custom scrim is fixed, `display: grid; place-items: center; padding: var(--space-16)`. Panel `color.surface.lifted`, `radius.panel`, `elevation.pinned`, padding `space.24`. Title `font.size.h3` semibold, body `space.8` below, actions `space.24` below: right-aligned, `space.8` apart, secondary then primary (mobile: full width, primary on top). Ghost close button top right. Destructive: danger primary.
|
|
6
|
+
|
|
7
|
+
**States.** The scrim only fades. Loading: primary loading, others disabled. Error: danger callout above the actions.
|
|
8
|
+
|
|
9
|
+
**A11y.** `role="dialog"` (`alertdialog` when destructive), `aria-modal`, `aria-labelledby`. Focus the least destructive control with its ring showing (`focus({ focusVisible: true })`); trap and return focus; Escape and a scrim click close it (not while loading).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### Empty state
|
|
2
|
+
|
|
3
|
+
**Use.** Nothing to show yet.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** Panel max 480px at the content column's left edge under a left-aligned h1 (centered only without an h1). `color.surface.lifted`, `radius.panel`, `elevation.pinned`, padding `space.40` `space.32`, left-aligned. Title `font.size.h3` semibold; one `color.text.secondary` sentence on what will appear; one secondary button (primary if creating is the page's job) `space.24` below; gaps `space.12`.
|
|
6
|
+
|
|
7
|
+
**Details.** Page-level: 1 tape strip, the empty-state bloom and a `.note--beside` note on the button restating it ("add the first one"). Inside a table, widget, cart or form: none, `color.surface.raised`, `elevation.rest`.
|
|
8
|
+
|
|
9
|
+
**States.** Failed to load: danger callout with retry.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"components": [
|
|
3
|
+
{ "name": "button", "category": "action", "status": "specified", "file": "button.md", "states": ["default", "hover", "focus-visible", "active", "disabled", "loading"] },
|
|
4
|
+
{ "name": "text-field", "category": "form", "status": "specified", "file": "text-field.md", "states": ["default", "hover", "focus-visible", "disabled", "error"] },
|
|
5
|
+
{ "name": "select", "category": "form", "status": "specified", "file": "select.md", "states": ["default", "hover", "focus-visible", "selected", "disabled", "error"] },
|
|
6
|
+
{ "name": "checkbox", "category": "form", "status": "specified", "file": "checkbox.md", "states": ["default", "hover", "focus-visible", "selected", "disabled", "error"] },
|
|
7
|
+
{ "name": "switch", "category": "form", "status": "specified", "file": "switch.md", "states": ["default", "hover", "focus-visible", "selected", "disabled", "loading"] },
|
|
8
|
+
{ "name": "tabs", "category": "navigation", "status": "specified", "file": "tabs.md", "states": ["default", "hover", "focus-visible", "selected", "disabled"] },
|
|
9
|
+
{ "name": "segmented-control", "category": "navigation", "status": "specified", "file": "segmented-control.md", "states": ["default", "hover", "focus-visible", "selected", "disabled"] },
|
|
10
|
+
{ "name": "navigation", "category": "navigation", "status": "specified", "file": "navigation.md", "states": ["default", "hover", "focus-visible", "selected"] },
|
|
11
|
+
{ "name": "chip", "category": "action", "status": "specified", "file": "chip.md", "states": ["default", "hover", "focus-visible", "selected", "disabled"] },
|
|
12
|
+
{ "name": "card", "category": "surface", "status": "specified", "file": "card.md", "states": ["default", "hover", "focus-visible", "loading", "error"] },
|
|
13
|
+
{ "name": "media-frame", "category": "surface", "status": "specified", "file": "media-frame.md", "states": ["default", "focus-visible", "selected", "loading", "error"] },
|
|
14
|
+
{ "name": "table", "category": "data-display", "status": "specified", "file": "table.md", "states": ["default", "hover", "focus-visible", "selected", "loading", "error"] },
|
|
15
|
+
{ "name": "badge", "category": "data-display", "status": "specified", "file": "badge.md", "states": ["default"] },
|
|
16
|
+
{ "name": "code-block", "category": "data-display", "status": "specified", "file": "code-block.md", "states": ["default", "focus-visible"] },
|
|
17
|
+
{ "name": "callout", "category": "feedback", "status": "specified", "file": "callout.md", "states": ["default", "focus-visible"] },
|
|
18
|
+
{ "name": "empty-state", "category": "feedback", "status": "specified", "file": "empty-state.md", "states": ["default", "error"] },
|
|
19
|
+
{ "name": "dialog", "category": "overlay", "status": "specified", "file": "dialog.md", "states": ["default", "focus-visible", "loading", "error"] },
|
|
20
|
+
{ "name": "toast", "category": "feedback", "status": "specified", "file": "toast.md", "states": ["default", "hover", "focus-visible"] }
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### Media frame
|
|
2
|
+
|
|
3
|
+
**Use.** Photos, screenshots and their placeholders.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** `radius.media`, `color.surface.tray`, `elevation.rest` (featured: `elevation.pinned`). Ratios 4:3 products, 16:10 screenshots, 3:2 posts; 3:2 in any grid of 3 or more asset-less frames. Thumbnails 64px, `radius.controlLg`. Hatch over the tray: `repeating-linear-gradient(135deg, var(--color-decor-hatch) 0 1px, transparent 1px 10px)`, 1.15:1 to 1.35:1, quieter than any text.
|
|
6
|
+
|
|
7
|
+
**States.** No asset (same in every app): tray, hatch, one centered `size.iconLg` outline image icon `color.text.tertiary`. Loading: tray and hatch only. Error: a 20px image-off icon and a `font.size.small` caption. Selected thumbnail: `0 0 0 2px` `color.action.primary` ring.
|
|
8
|
+
|
|
9
|
+
**A11y.** Meaningful `alt` (empty when decorative); thumbnails are buttons with `aria-pressed`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
### Navigation
|
|
2
|
+
|
|
3
|
+
**Use.** Header for sections; sidebar from 6 sections.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** Header `size.header` (mobile `size.headerMobile`), `color.surface.default`, 1px `color.border.default` bottom, the page container, never `overflow: hidden`. Product name as text (`font.size.lead`, semibold, `font.tracking.title`). Links `font.size.ui` `color.text.secondary`, each a `size.controlSm` box, padding 0 `space.8`, `radius.inner`, `space.8` apart; the focus outline follows the box. Search (`size.controlSm`) and one action at the right. Sidebar 220px: mono eyebrows, items `size.controlSm`, padding 0 `space.10`, `radius.inner`.
|
|
6
|
+
|
|
7
|
+
**Mobile.** More than 3 links: product name, at most 1 action, a `size.touch` menu button opening a sheet below the header (styled as the Select menu, rows `size.touch`, global search first at full width).
|
|
8
|
+
|
|
9
|
+
**States.** Hover `color.text.primary` (sidebar item also `color.surface.subtle`). Current: header link `color.text.primary`, medium, no pill or underline; sidebar item `color.surface.subtle`.
|
|
10
|
+
|
|
11
|
+
**A11y.** `<nav aria-label>`, `aria-current="page"`; menu button `aria-expanded`; Escape closes the sheet.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### Segmented control
|
|
2
|
+
|
|
3
|
+
**Use.** 2 to 4 short modes of the same data.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** Track `color.surface.subtle`, padding 3px, `radius.control`, height `size.control`. Segments `size.controlSm`, padding 0 `space.12`, `font.size.small`, `radius.inner`, `color.text.secondary`.
|
|
6
|
+
|
|
7
|
+
**States.** Hover `color.text.primary`. Selected: `color.surface.selected`, `box-shadow: var(--elevation-segment), inset 0 0 0 1px var(--color-border-selected)` (dark ring 4.6:1 on the track), `color.text.primary`, medium.
|
|
8
|
+
|
|
9
|
+
**A11y.** `radiogroup` of `radio`; Arrows move.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### Select
|
|
2
|
+
|
|
3
|
+
**Use.** One of 5 or more options; 2 to 4 use the segmented control or radios.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** Trigger as Text field with `appearance: none`, `padding-right: space.32` and a 16px chevron `color.text.secondary` at `space.12` from the right. Menu: `color.surface.lifted`, `radius.media`, `elevation.pinned`, padding `space.4`, max height 320px; options `size.controlMd`, padding 0 `space.10`, `radius.inner`.
|
|
6
|
+
|
|
7
|
+
**States.** Hover: trigger border `color.border.strong`; active option `color.surface.subtle`. Selected option: `font.weight.medium`, trailing check.
|
|
8
|
+
|
|
9
|
+
**A11y.** Prefer a native `<select>`; custom: `combobox` plus `listbox`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### Switch
|
|
2
|
+
|
|
3
|
+
**Use.** A setting that applies immediately; settings that wait for Save use a checkbox.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** The control itself is the track and takes every click: `<input type="checkbox" role="switch">` (or an existing `<button role="switch">`) with `appearance: none; box-sizing: border-box; margin: 0; padding: 0; position: relative`, 36 x 20px, `radius.full`, 1px `color.text.tertiary` border, `color.surface.subtle`. Thumb: its `::before`, 16px circle at `top: 1px; left: 1px`, `color.text.tertiary` (4.6:1 on the track); a thumb span gets `pointer-events: none`. Rows: label left, switch right, min height `size.touch`.
|
|
6
|
+
|
|
7
|
+
**States.** Hover: border and off thumb `color.text.secondary`. On (`:checked`, `[aria-checked="true"]`): track and border `color.action.primary`; thumb `translate: 16px 0` over `motion.duration.fast`, `color.text.onAction`, `elevation.segment`; never green. Loading: 10px spinner in the thumb; on failure revert and show a danger toast.
|
|
8
|
+
|
|
9
|
+
**A11y.** `aria-checked`; Space toggles; the visible label is the name.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
### Table
|
|
2
|
+
|
|
3
|
+
**Use.** Records compared by the same attributes. Key/value data: spec rows (180px key `color.text.secondary`, hairlines, `space.14` vertical padding; key above value on mobile).
|
|
4
|
+
|
|
5
|
+
**Anatomy.** Frame `color.surface.raised`, `elevation.rest`, `radius.panel`, clipped. Rows `size.row`, cells padding 0 `space.12`, `font.size.small`. Header sans caption, medium, `color.text.secondary`, hairline below. `table-layout: fixed` with `<col>` widths: numbers and dates sized to their longest value, status 120px, actions to their buttons, the primary column takes the rest, so filtering moves no column. Numbers right-aligned; row actions last, right-aligned.
|
|
6
|
+
|
|
7
|
+
**Mobile (under `breakpoint.md`): stack.** A table with a status or action column or more than 4 columns becomes one block per row. Keep the `<table>`, add `role` `table`/`rowgroup`/`row`/`columnheader`/`cell`, and a `data-label` per `<td>`. Columns become label/value pairs (max 6; never drop status, actions or the active sort key); sort chips list only shown keys.
|
|
8
|
+
|
|
9
|
+
```css
|
|
10
|
+
@media (max-width: 859px) {
|
|
11
|
+
.table--stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
12
|
+
.table--stack, .table--stack :is(tbody, tr, td) { display: block; }
|
|
13
|
+
.table--stack tr { padding: var(--space-14) var(--space-16); border-bottom: 1px solid var(--color-border-default); }
|
|
14
|
+
.table--stack td { display: grid; grid-template-columns: minmax(96px, 38%) 1fr; gap: var(--space-12); padding: var(--space-4) 0; text-align: left; }
|
|
15
|
+
.table--stack td:not(.cell-primary, .cell-actions)::before { content: attr(data-label); font-size: var(--font-size-caption); color: var(--color-text-tertiary); }
|
|
16
|
+
.table--stack .cell-primary { display: block; font-size: var(--font-size-ui); font-weight: var(--font-weight-medium); padding-bottom: var(--space-6); }
|
|
17
|
+
.table--stack .cell-actions { display: flex; gap: var(--space-8); padding-top: var(--space-12); }
|
|
18
|
+
.table--stack .cell-actions > * { flex: 1; height: var(--size-touch); }
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Mobile: scroll.** Other tables scroll in `<div class="table-scroll" role="region" tabindex="0" aria-label="{name}, scrolls sideways">`, a column cut mid-cell. A script sets `data-overflow` and `data-at-end`; while content is off-screen the edge fades and a caption "Scroll sideways" hint sits right-aligned above.
|
|
23
|
+
|
|
24
|
+
```css
|
|
25
|
+
.table-scroll { overflow-x: auto; min-width: 0; }
|
|
26
|
+
.table-scroll[data-overflow]:not([data-at-end]) { mask-image: linear-gradient(to left, transparent, black 32px); }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**States.** Hover: `color.surface.subtle` at 50%. Selected: `color.surface.subtle` plus `inset 2px 0 0` `color.action.primary` on the first cell. Sorted header: `color.text.primary`, 12px arrow. Loading: 5 skeleton rows. Error: danger callout with retry. Empty: empty state without details. Focus: inset.
|
|
30
|
+
|
|
31
|
+
**A11y.** `<th scope>`; sortable headers are buttons with `aria-sort`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### Tabs
|
|
2
|
+
|
|
3
|
+
**Use.** Sibling views of one object; filtering one dataset uses the segmented control.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** Tab list over a 1px `color.border.default` line; tabs `size.touch` tall, `space.24` apart, `font.size.ui`; panel `space.24` below; overflow scrolls inside the list.
|
|
6
|
+
|
|
7
|
+
**States.** Default `color.text.secondary`; hover `color.text.primary`. Selected: `color.text.primary`, semibold, underline `inset 0 -2px 0` `color.action.primary`.
|
|
8
|
+
|
|
9
|
+
**A11y.** `tablist`, `tab` with `aria-selected`, `tabpanel`; Left and Right move.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
### Text field
|
|
2
|
+
|
|
3
|
+
**Use.** Single-line input, including search. Textarea: min height 96px, padding `space.10` `space.12`, vertical resize.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** Label above (`font.size.small`, medium, `color.text.primary`), optional hint (`font.size.small`, `color.text.secondary`). Input height per rule 8, `box-sizing: border-box`, padding 0 `space.12`, `font.size.ui`, `radius.control`, `color.surface.raised`, 1px `color.border.control`, placeholder `color.text.tertiary`; in a flex row `flex: 1 1 240px; min-width: 0`. Gaps `space.6`; fields `space.20` apart.
|
|
6
|
+
|
|
7
|
+
**Search.** `input::-webkit-search-cancel-button, input::-webkit-search-decoration { -webkit-appearance: none }`. A clear control, if the app has one: 24px ghost icon button in the right padding (input `padding-right: space.32`), 16px x `color.text.secondary`, `aria-label="Clear search"`, only with a value.
|
|
8
|
+
|
|
9
|
+
**States.** Hover: border `color.border.strong`. Read-only: `color.surface.subtle`.
|
|
10
|
+
|
|
11
|
+
**A11y.** Visible `<label for>`, never placeholder-only.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
### Toast
|
|
2
|
+
|
|
3
|
+
**Use.** Confirms every save or submit, or a non-blocking failure.
|
|
4
|
+
|
|
5
|
+
**Anatomy.** Ground `color.action.primary`, text and 16px icon `color.text.onAction`, `elevation.segment`, `radius.callout`, min height `size.controlLg`, padding `space.12` `space.16`, gap `space.10`, max width 420px, `font.size.ui`. Optional action: underlined medium text button. Fixed `space.24` from the bottom right; under `breakpoint.md` `space.16` from the sides, above the action bar. Max 3, `space.8` apart, newest at the bottom. Tones differ only by icon and wording.
|
|
6
|
+
|
|
7
|
+
**States.** Hover pauses dismissal. Focus: 2px `color.text.onAction` outline, 2px offset.
|
|
8
|
+
|
|
9
|
+
**A11y.** `role="status"` (`alert` for danger). Auto-dismiss after 5s unless it has an action; Escape dismisses.
|