@vegastack/design 0.4.0 → 0.5.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/dist/chunk-42HJ4OYZ.js +103 -0
- package/dist/create-animated-icon.cjs +2 -52
- package/dist/create-animated-icon.d.cts +1 -1
- package/dist/create-animated-icon.d.ts +1 -1
- package/dist/create-animated-icon.js +2 -2
- package/dist/icons/index.cjs +5 -55
- package/dist/icons/index.d.cts +13 -6
- package/dist/icons/index.d.ts +13 -6
- package/dist/icons/index.js +5 -5
- package/dist/index.cjs +17 -102
- package/dist/index.d.cts +25 -168
- package/dist/index.d.ts +25 -168
- package/dist/index.js +3 -15
- package/dist/theme-scope.d.cts +1 -1
- package/dist/theme-scope.d.ts +1 -1
- package/package.json +3 -3
- package/skills/vegastack-brand/SKILL.md +3 -2
- package/skills/vegastack-design-audit/SKILL.md +41 -20
- package/skills/vegastack-design-system/SKILL.md +100 -96
- package/skills/vegastack-design-system/references/components.md +60 -70
- package/dist/chunk-LRQSVCP6.js +0 -182
|
@@ -54,33 +54,53 @@ rg -n 'style=\{\{' --glob '!components/ui/**'
|
|
|
54
54
|
- **hardcoded colour** — a hex literal used as a style value. Use a semantic token. **error**
|
|
55
55
|
- **raw palette** — a Tailwind palette class. Use `bg-primary`, `text-muted-foreground`,
|
|
56
56
|
`border-border`, or a status family. **error**
|
|
57
|
-
- **hardcoded dimension** — an arbitrary px/rem value
|
|
58
|
-
|
|
57
|
+
- **hardcoded dimension** — an arbitrary px/rem value where a stock utility says the same thing
|
|
58
|
+
(`h-[32px]` for `h-8`, `rounded-[10px]` for `rounded-lg`). An arbitrary value that no utility
|
|
59
|
+
expresses is not a finding; upstream writes several itself. **warning**
|
|
59
60
|
- **inline style** — allowed only when every key is a `--*` custom property. Any direct visual
|
|
60
61
|
property is a finding. **error**
|
|
61
62
|
|
|
62
63
|
## 3. Off-system utilities
|
|
63
64
|
|
|
64
65
|
```bash
|
|
65
|
-
rg -n '\b(
|
|
66
|
+
rg -n '#[0-9a-fA-F]{3,8}\b|\b(bg|text|border|ring|fill|stroke)-(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-[0-9]{2,3}\b' --glob '!components/ui/**'
|
|
67
|
+
rg -n 'ring-3\b|ring-\[3px\]|ring-ring/[0-9]+|focus-visible:ring-|shadow-\[0_0_0_' --glob '!components/ui/**'
|
|
66
68
|
```
|
|
67
69
|
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
- a
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
- a raw
|
|
77
|
-
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
- `
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
- a hex literal or a numbered Tailwind palette utility — use a semantic token. **error**
|
|
71
|
+
- **a focus-ring glow** — `ring-3`, `ring-[3px]`, `ring-ring/NN`, `focus-visible:ring-*` or a
|
|
72
|
+
`0 0 0` box-shadow ring. The system has ONE focus affordance, the global `:focus-visible` outline;
|
|
73
|
+
text entry tints its border instead. A glow usually means a component was pasted from upstream's
|
|
74
|
+
docs without the patch. **error**
|
|
75
|
+
- a status FILL used as a text ink on that family's own tint — `bg-destructive/10 text-destructive`
|
|
76
|
+
measures 3.98:1. The readable half is `text-destructive-text`. **error**
|
|
77
|
+
- `text-brand` used as a label — `brand` is a 3:1 marker; labels take `text-brand-text`. **error**
|
|
78
|
+
- a raw `<svg>` used as an icon — use lucide or `Icon`/`BrandIcon`. **error**
|
|
79
|
+
- `React.forwardRef` — React 19 takes `ref` as a normal prop. **error**
|
|
80
|
+
|
|
81
|
+
**Things that are NOT findings any more**, and reporting them is noise: `rounded-xl`, `shadow-md`,
|
|
82
|
+
`text-4xl`, `font-semibold`, `tracking-tight`, `transition-all`, `transition-colors`,
|
|
83
|
+
`duration-100`, `ease-in-out`, `z-50`, `opacity-50`, a raw `/NN` alpha, `h-8`/`size-4`,
|
|
84
|
+
`cursor-default` on a menu row, an arbitrary `h-[18.4px]`, and a `hover:` with no `active:` beside
|
|
85
|
+
it. Every one of those is upstream's own vocabulary, which this system now adopts.
|
|
86
|
+
|
|
87
|
+
## 3b. Names and tokens the shadcn reset removed
|
|
88
|
+
|
|
89
|
+
A project upgrading across the reset carries these until someone changes them, and **there is no
|
|
90
|
+
compatibility layer** — an import resolves to nothing and a deleted token silently compiles to
|
|
91
|
+
nothing, which is the worse half. Both searches are mechanical:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
rg -n 'IconButton|OTPInput|PasswordInput|CheckboxGroup|FieldInline|Segmented|SplitButton|ProgressIndicator|OnboardingChecklist|FloatingSurface|MarketingSurface|ComparisonMatrix|FigureFrame|LogoRow|ParticleField|PricingSection|RuledBand|SectionHeader|Testimonial|StaggeredTextReveal' --glob '!components/ui/**'
|
|
95
|
+
rg -n 'surface-(1|2|3|raised)|--alpha-|--opacity-|--size-|--icon-|--panel-width-|--z-(raised|overlay|toast)|shadow-overlay|text-(h[1-4]|label|label-sm|code|mono-label|display-)|muted-foreground-faint|surfaceInteractive|fillInteractive|fieldControl|selectedChipVariants' --glob '!components/ui/**'
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- **a retired component name** — each has a replacement, listed in the shadcn-reset migration guide;
|
|
99
|
+
the marketing ten have none and their markup is the app's now. **error**
|
|
100
|
+
- **a deleted token or utility** — a `bg-surface-2` or a `text-h1` resolves to nothing and paints the
|
|
101
|
+
inherited value, so the page looks subtly wrong rather than broken. **error**
|
|
102
|
+
- **a deleted `@vegastack/design` export** — `surfaceInteractive`, `fillInteractive`, `fieldControl`,
|
|
103
|
+
`fieldControlGroup`, `selectedChipVariants`. Replace each with the literal it expanded to. **error**
|
|
84
104
|
|
|
85
105
|
## 4. Component substitution and accessibility
|
|
86
106
|
|
|
@@ -91,7 +111,8 @@ string, or it silently inherits a default curve. **warning**
|
|
|
91
111
|
and `Icon`/`BrandIcon` from `@vegastack/design/icons` are sanctioned. **error**
|
|
92
112
|
- **`outline-none` with no replacement focus affordance** anywhere in the file. **error**
|
|
93
113
|
- **Icon-only controls with no accessible name** — a button with no visible text needs `aria-label`
|
|
94
|
-
or `aria-labelledby`.
|
|
114
|
+
or `aria-labelledby`. `<Button size="icon">` (and `icon-xs`/`icon-sm`/`icon-lg`) is the one
|
|
115
|
+
icon-only control; the name is never optional. **error**
|
|
95
116
|
- **Missing states** — a surface that fetches data needs loading, empty, and error states, not just
|
|
96
117
|
the success path. **warning**
|
|
97
118
|
- **Truncation** — `truncate`/`line-clamp-*` on the same element as `flex`/`inline-flex` silently
|
|
@@ -5,12 +5,32 @@ description: Build product UI with the VegaStack design system — which compone
|
|
|
5
5
|
|
|
6
6
|
# VegaStack design system
|
|
7
7
|
|
|
8
|
-
Base UI + Tailwind v4 + OKLCH semantic tokens. Components are copy-in
|
|
9
|
-
the runtime and token layer are public npm.
|
|
8
|
+
Base UI + Tailwind v4 + OKLCH semantic tokens, on shadcn's `base-nova` style. Components are copy-in
|
|
9
|
+
via a private shadcn registry; the runtime and token layer are public npm.
|
|
10
10
|
|
|
11
11
|
Load this before writing UI code. For first-time project setup (installing packages, wiring the
|
|
12
12
|
provider, configuring registry access), use the `vegastack-consume` skill instead.
|
|
13
13
|
|
|
14
|
+
**The shadcn reset is a clean break, with no compatibility layer** — no aliases, no deprecation
|
|
15
|
+
shims, no re-exports. It ships as an ordinary minor release, so the version number does not warn you;
|
|
16
|
+
this section does. Every component shadcn ships is now upstream's own file, so its API is upstream's
|
|
17
|
+
API. The complete break is in the shadcn-reset migration guide that ships with the release notes; the
|
|
18
|
+
live contract for any single component is its page at
|
|
19
|
+
<https://design.vegastack.com/docs/components>. The headlines, because they decide most code an agent
|
|
20
|
+
writes:
|
|
21
|
+
|
|
22
|
+
- **Retired, with no drop-in:** `IconButton` → `Button size="icon*"` · `OTPInput` → `InputOTP` ·
|
|
23
|
+
`PasswordInput` → an `InputGroup` composition · `CheckboxGroup` → `FieldSet` + `Checkbox` ·
|
|
24
|
+
`FieldInline` → `EditableCell` · `Segmented` → a joined `ToggleGroup` · `SplitButton` → a
|
|
25
|
+
`ButtonGroup` composition · `ProgressIndicator` → `Progress` + `Spinner` · `OnboardingChecklist` →
|
|
26
|
+
the `onboarding-01` block. The ten marketing components were deleted outright.
|
|
27
|
+
- **Gone from the token layer:** the surface ladder (`surface-1/2/3`), every `--alpha-*` and
|
|
28
|
+
`--opacity-*`, `--size-*`, `--icon-*`, `--z-*`, `--shadow-overlay`, and the role type scale
|
|
29
|
+
(`text-h1`, `text-label`, `text-code`, `text-mono-label`, `text-display-*`).
|
|
30
|
+
- **Gone from `@vegastack/design`:** `surfaceInteractive`, `surfaceInteractiveGroup`,
|
|
31
|
+
`fillInteractive`, `FillTone`, `fieldControl`, `fieldControlGroup`, `selectedChipVariants`.
|
|
32
|
+
`cn`, `TIMINGS`, `FLOATING`, `mergeRefs`, `prose`/`proseClassName` and the icon runtime all stay.
|
|
33
|
+
|
|
14
34
|
## Pick a component
|
|
15
35
|
|
|
16
36
|
[references/components.md](references/components.md) is the complete roster, grouped by family, with
|
|
@@ -25,23 +45,28 @@ pnpm dlx shadcn@latest list @vegastack
|
|
|
25
45
|
|
|
26
46
|
Rules that decide most component questions:
|
|
27
47
|
|
|
28
|
-
- **`Button` is
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
size
|
|
48
|
+
- **`Button` is one axis** — `variant` is `default · outline · secondary · ghost · destructive ·
|
|
49
|
+
link` (upstream's set, verbatim). `destructive` is a soft tint, not a solid red fill. Icon-only
|
|
50
|
+
actions are `<Button size="icon">` (or `icon-xs` / `icon-sm` / `icon-lg`) with an `aria-label`;
|
|
51
|
+
an icon in a bare `<button>` is off-system. An icon-only LINK stays an `<a>` styled with
|
|
52
|
+
`buttonVariants({ variant, size: "icon" })` — never a `Button`, which would put `role="button"`
|
|
53
|
+
on navigation. `loading` is ours: it holds the label's box and sets `aria-busy`.
|
|
54
|
+
- **Control sizes are upstream's names: `default · xs · sm · lg`**, plus
|
|
55
|
+
`icon · icon-xs · icon-sm · icon-lg` where a square tier exists. The old `md` default is gone, and
|
|
56
|
+
most of the components that are ours dropped their `size` prop entirely and take their height from
|
|
57
|
+
what they compose. Four keepers still carry a small private axis over something that is not a
|
|
58
|
+
control height — `Chip` (`sm`/`md`, the inline and control pill scales), `StatusIcon`, `Stat` and
|
|
59
|
+
`Image`'s corner — and they say so on their own pages.
|
|
37
60
|
- **Compose `app-shell`** for a sidebar + header + main layout — never hand-roll the landmark trio.
|
|
38
61
|
- **`select`** for a short fixed option set; **`searchable-select`** when the list is long enough to
|
|
39
62
|
need a search field (it is the preset `country-select` and `region-select` are built from — reach
|
|
40
63
|
for it before composing `combobox` by hand); **`combobox`** directly only for free text,
|
|
41
64
|
suggestions or multi-select chips.
|
|
42
|
-
- **`
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
- **`toggle-group`** with `spacing={0}` for 2–5 exclusive options inline; **`tabs`** when the
|
|
66
|
+
choice switches page regions.
|
|
67
|
+
- **`alert`** for an in-content notice — `variant` is `default · destructive · success · warning ·
|
|
68
|
+
info`, each an ink on the `card` surface with a required icon; **`announcement-banner`** only for
|
|
69
|
+
the full-width inverse strip at the very top of the page.
|
|
45
70
|
- **`chip` is the ONE pill** — `hue` × `size` (`sm` inline · `md` control-scale) × `active`, with
|
|
46
71
|
`onRemove` giving a real 24×24 remove control. `Tag`, `FilterChip` and `ComboboxChip` are that
|
|
47
72
|
primitive composed through `render`; never hand-roll a pill with its own height, radius, or a
|
|
@@ -52,49 +77,47 @@ Rules that decide most component questions:
|
|
|
52
77
|
`role="status"` node with a `{text, seq}` counter.
|
|
53
78
|
- **`code-block`** for static syntax-highlighted source; **`terminal`** for command sessions.
|
|
54
79
|
- **`navigation-menu`** is top-level site navigation with panels, not a menu inside a page.
|
|
55
|
-
- **Marketing components** (`marketing-surface`, `section-header`, `figure-frame`, `terminal`,
|
|
56
|
-
`logo-row`, `testimonial`, `staggered-text-reveal`, `particle-field`, `pricing-section`, and
|
|
57
|
-
Button's `cta` variant) are scoped to `.vs-marketing` and must never appear in product UI.
|
|
58
80
|
|
|
59
81
|
## Tokens
|
|
60
82
|
|
|
61
|
-
Semantic CSS custom properties from `@vegastack/design-tokens/theme.css` (OKLCH, `:root` + `.dark`)
|
|
62
|
-
Always use the utility, never a raw value.
|
|
63
|
-
|
|
64
|
-
| Role | Utilities
|
|
65
|
-
| -------- |
|
|
66
|
-
| Surface | `bg-background` (page) · `bg-card`
|
|
67
|
-
|
|
|
68
|
-
| Text | `text-foreground` `text-muted-foreground` `text-{primary,accent,popover}-foreground`
|
|
69
|
-
| Status | `bg-{destructive,success,warning,info}`
|
|
70
|
-
| Border | `border-border` `border-input` — there are no rings; focus is
|
|
71
|
-
| Radius | `rounded-{
|
|
72
|
-
| Type | `text-{xs…
|
|
73
|
-
| Font | `font-sans` `font-mono` `font-serif`
|
|
74
|
-
| Motion | `duration-{fast,base,slow}`
|
|
75
|
-
| Entrance | `motion-pop-in` `motion-enter-up` `motion-shake` `motion-flash`
|
|
76
|
-
| Docked | `motion-dock-in` / `motion-dock-out` — a control parked at a viewport edge, 150ms in / 100ms out
|
|
77
|
-
| Prose | `proseClassName` from `@vegastack/design` — the whole rendered-rich-text recipe, one class
|
|
78
|
-
|
|
79
|
-
**Hover and pressed
|
|
80
|
-
|
|
83
|
+
Semantic CSS custom properties from `@vegastack/design-tokens/theme.css` (OKLCH, `:root` + `.dark`),
|
|
84
|
+
on shadcn's `neutral` base. Always use the utility, never a raw value.
|
|
85
|
+
|
|
86
|
+
| Role | Utilities |
|
|
87
|
+
| -------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
88
|
+
| Surface | `bg-background` (page) · `bg-card` · `bg-popover` · `bg-sidebar` |
|
|
89
|
+
| Fill | `bg-primary` (solid action, every checked control) · `bg-secondary` (soft) · `bg-muted` (well, track, skeleton) · `bg-accent` (hover) |
|
|
90
|
+
| Text | `text-foreground` · `text-muted-foreground` · `text-{primary,secondary,accent,card,popover}-foreground` |
|
|
91
|
+
| Status | `bg-{destructive,success,warning,info}` · `-foreground` (ink ON the fill) · `-text` (ink on the page or on the family's own tint) |
|
|
92
|
+
| Border | `border-border` · `border-input` — there are no rings; focus is one global outline |
|
|
93
|
+
| Radius | `rounded-{sm,md,lg,xl,2xl}` — all derived from the single `--radius` |
|
|
94
|
+
| Type | Tailwind's own `text-{xs…7xl}`. `text-sm` is 14px, `text-base` is 16px |
|
|
95
|
+
| Font | `font-sans` `font-mono` `font-serif` `font-heading` |
|
|
96
|
+
| Motion | `duration-{fast,base,slow}` · `ease-{standard,emphasized,exit,spring}` — or Tailwind's own steps |
|
|
97
|
+
| Entrance | `motion-pop-in` `motion-enter-up` `motion-shake` `motion-flash` |
|
|
98
|
+
| Docked | `motion-dock-in` / `motion-dock-out` — a control parked at a viewport edge, 150ms in / 100ms out |
|
|
99
|
+
| Prose | `proseClassName` from `@vegastack/design` — the whole rendered-rich-text recipe, one class |
|
|
100
|
+
|
|
101
|
+
**Hover and pressed are written, not imported.** A component owns its own interaction chrome, the
|
|
102
|
+
way shadcn writes it:
|
|
81
103
|
|
|
82
104
|
```tsx
|
|
83
|
-
import { cn, surfaceInteractive, fillInteractive } from "@vegastack/design";
|
|
84
|
-
|
|
85
105
|
// A transparent control on a known surface.
|
|
86
|
-
<button className=
|
|
87
|
-
|
|
106
|
+
<button className="rounded-md px-2 hover:bg-accent hover:text-accent-foreground" />;
|
|
107
|
+
|
|
108
|
+
// A solid.
|
|
109
|
+
<button className="bg-primary text-primary-foreground hover:bg-primary/80" />;
|
|
88
110
|
|
|
89
|
-
// A
|
|
90
|
-
<button className=
|
|
91
|
-
// hover:bg-destructive/(--alpha-hover) active:bg-destructive/(--alpha-pressed)
|
|
111
|
+
// A tinted status control. The ink on a tint is `-text`, never the fill.
|
|
112
|
+
<button className="bg-destructive/10 text-destructive-text hover:bg-destructive/20" />;
|
|
92
113
|
```
|
|
93
114
|
|
|
94
|
-
A
|
|
115
|
+
A pressed step is optional. `surfaceInteractive`, `fillInteractive`, `fieldControl`,
|
|
116
|
+
`fieldControlGroup` and `selectedChipVariants` were **deleted** from `@vegastack/design` with no
|
|
117
|
+
alias; if you are upgrading, replace each with the literal it expanded to.
|
|
95
118
|
|
|
96
|
-
**Rendered rich text comes from a recipe
|
|
97
|
-
|
|
119
|
+
**Rendered rich text comes from a recipe.** Anything the system did not author element by element —
|
|
120
|
+
markdown, a contenteditable, CMS copy — wears one class on its root:
|
|
98
121
|
|
|
99
122
|
```tsx
|
|
100
123
|
import { cn, proseClassName } from "@vegastack/design";
|
|
@@ -110,40 +133,15 @@ descendant rules (`[&_h1]:…`), which means an element-level class on a child *
|
|
|
110
133
|
(specificity (0,1,0) against (0,1,1)) — restyle by composing `prose` (the per-element record), never
|
|
111
134
|
by setting a class on the rendered element.
|
|
112
135
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
a selected look — it is the same formula `Tabs`, `Segmented` and `Toggle` use:
|
|
116
|
-
|
|
117
|
-
```tsx
|
|
118
|
-
import { cn, selectedChipVariants } from "@vegastack/design";
|
|
119
|
-
|
|
120
|
-
<div className={cn("rounded-md p-0.5", selectedChipVariants.track)}>
|
|
121
|
-
<Toggle
|
|
122
|
-
className={cn(
|
|
123
|
-
"rounded-sm",
|
|
124
|
-
selectedChipVariants.item,
|
|
125
|
-
selectedChipVariants.pressed,
|
|
126
|
-
)}
|
|
127
|
-
/>
|
|
128
|
-
</div>;
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
Use `.pressed` for a control whose selected state is Base UI's `data-pressed` and `.active` for one
|
|
132
|
-
using `data-active`. The selected chip keeps its own hover and pressed steps — never guard them off.
|
|
133
|
-
|
|
134
|
-
`secondary`, `muted`, `accent` and the `sidebar-*` family are **aliases** of ladder rungs
|
|
135
|
-
(`secondary` = `muted` = `surface-1`, `accent` = `sidebar-accent` = `surface-2`, `sidebar` = `card`).
|
|
136
|
-
They still compile; name the rung in new code.
|
|
137
|
-
|
|
138
|
-
`border` is one translucent hairline — `foreground` at `--alpha-border` — so it reads on the page, on
|
|
139
|
-
a card and inside a well alike. `info` is **links and informational UI only**: promotion and
|
|
140
|
-
selection take a ladder rung or `primary`.
|
|
136
|
+
`muted`, `accent` and `secondary` share one value in this base, and all three are kept: name the one
|
|
137
|
+
whose ROLE you mean, so a consumer can retune one without moving the others.
|
|
141
138
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
**Status colour has two inks.** `-foreground` is the ink on the solid fill; `-text` is the ink on the
|
|
140
|
+
page and on the family's own `/10`-`/30` tint. Using the fill itself as text on a tint measures
|
|
141
|
+
3.98-4.35:1, which the contrast gate rejects. `info` is links and informational UI only.
|
|
145
142
|
|
|
146
|
-
`--brand` is a marker-role accent only — never a functional state colour
|
|
143
|
+
`--brand` is a marker-role accent only — never a functional state colour, and never a text ink
|
|
144
|
+
(`--brand-text` is the readable half).
|
|
147
145
|
|
|
148
146
|
**Overriding tokens:** redefine one runtime variable in your global CSS and every component repaints
|
|
149
147
|
in both themes:
|
|
@@ -159,18 +157,19 @@ contract.
|
|
|
159
157
|
|
|
160
158
|
## Composition patterns
|
|
161
159
|
|
|
162
|
-
- **Forms** —
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
belongs
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
-
|
|
169
|
-
|
|
170
|
-
|
|
160
|
+
- **Forms are composed, not configured** — `Field` is layout and copy: `FieldLabel` bound with
|
|
161
|
+
`htmlFor`, the control, then `FieldDescription` and `FieldError` as CHILDREN. There is no `label`,
|
|
162
|
+
`description` or `error` prop, and no context that reaches into the control. State is written where
|
|
163
|
+
it belongs: `aria-invalid` on the control (for assistive tech), `data-invalid` / `data-disabled` on
|
|
164
|
+
the `Field` (for the block's styling). `FieldError` is `role="alert"`, carries a leading icon so an
|
|
165
|
+
error is never colour alone, and takes either children or an `errors` array it de-duplicates.
|
|
166
|
+
react-hook-form's `register` wires straight to the control; there is no `Controller` indirection.
|
|
167
|
+
- **A set of related checkboxes is a `FieldSet` + `FieldLegend` + one `Field` per option** — that is
|
|
168
|
+
the composition upstream documents, and it is what `Checkbox`'s own docs page shows. Compute
|
|
169
|
+
`checked` / `indeterminate` for a select-all parent in your own state, as the Table example does.
|
|
171
170
|
- **Click-to-edit is `useInlineEdit`** — draft, commit, cancel, focus restoration and the
|
|
172
|
-
double-commit guard, with no opinion about the editor or the display. `
|
|
173
|
-
|
|
171
|
+
double-commit guard, with no opinion about the editor or the display. `EditableCell` is built
|
|
172
|
+
on it.
|
|
174
173
|
- **Overlays** — enter/exit is driven by `data-starting-style`/`data-ending-style` on the popup root,
|
|
175
174
|
inside a portal + positioner. Theme, toast, tooltip, and direction providers all come from
|
|
176
175
|
`<VegaStackProvider>`; your app root needs `isolation: isolate` or portaled popups can render under
|
|
@@ -192,20 +191,25 @@ contract.
|
|
|
192
191
|
chrome.
|
|
193
192
|
- Implement every applicable state: default, hover, focus, loading, empty, error, success, disabled.
|
|
194
193
|
- Put `truncate` on an inner span, with `min-w-0` on the flex container.
|
|
195
|
-
- Let the parent decide a form control's width — every control is `w-full
|
|
196
|
-
|
|
194
|
+
- Let the parent decide a form control's width — every control is `w-full`.
|
|
195
|
+
- Reach for a plain Tailwind utility for size, radius, shadow, z-index, alpha, weight and motion:
|
|
196
|
+
`h-8`, `size-4`, `rounded-xl`, `shadow-md`, `z-50`, `bg-foreground/10`, `opacity-50`,
|
|
197
|
+
`font-semibold`, `transition-colors duration-100 ease-in-out` are all on-system now.
|
|
197
198
|
|
|
198
199
|
**Don't**
|
|
199
200
|
|
|
200
201
|
- Hardcode a hex, a px value, or a raw Tailwind palette class (`bg-neutral-900`, `text-red-500`).
|
|
201
|
-
-
|
|
202
|
-
|
|
202
|
+
- Add a focus ring or glow. Focus is one global outline, and text entry tints its border instead;
|
|
203
|
+
`ring-3`, `ring-ring/50` and `focus-visible:ring-*` are rejected by lint.
|
|
203
204
|
- Set `outline-none` without providing another focus affordance.
|
|
204
|
-
-
|
|
205
|
-
|
|
205
|
+
- Use a status FILL as ink on its own tint — `bg-destructive/10 text-destructive` measures 3.99:1.
|
|
206
|
+
The ink on a tint is `-text`.
|
|
207
|
+
- Pull in a second icon library, hand-write an inline `<svg>` as an icon, or pass
|
|
208
|
+
`size`/`width`/`height` to a lucide component.
|
|
206
209
|
- Hand-roll a removable pill, or a `role="status"` live region with its own sequence counter.
|
|
207
210
|
- Give a form control a fixed width (`w-56`, `w-64`) — it reads fine on the page it was tuned for
|
|
208
211
|
and overflows at 320px. Constrain the parent instead.
|
|
212
|
+
- Expect a compatibility shim from before the reset. There is none — see the migration guide.
|
|
209
213
|
|
|
210
214
|
## Reference
|
|
211
215
|
|