一键导入
design-tokens
Design token conventions for the @keystoneui/react component library. Apply when creating or editing UI components in packages/ui/.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Design token conventions for the @keystoneui/react component library. Apply when creating or editing UI components in packages/ui/.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Component workflow for keystoneui — triggered when adding, creating, scaffolding, or modifying components, demos, stories, MDX docs, or any AI-agent surface (SKILL.md, AGENTS.md, llms.txt, MCP registry).
Manages Keystone UI components and projects — adding, searching, fixing, debugging, styling, and composing UI built on Tailwind CSS v4 + Base UI. Provides project context, component docs, and usage examples. Applies when working with Keystone UI, @keystoneui/react, components.json with @keystoneui/* registries, or any project with @keystoneui/react in its dependencies. Also triggers for "keystoneui add", "find a Keystone UI example", or "switch to Keystone UI".
Component architecture conventions for the @keystoneui/react package. Apply when creating, editing, or reviewing UI components in packages/ui/.
Popup component conventions for DropdownMenu, Select, Combobox, and Popover. Apply when creating or editing popup/overlay components in packages/ui/.
Use this agent to run a systematic audit of the Keystone UI codebase against all project skills and rules. This covers component architecture, design tokens, documentation quality, React performance, and animation patterns. Use before releases, after large refactors, or periodically to catch drift. Triggers on keywords like audit codebase, check compliance, review practices, pre-release check, quality audit, scan for violations, check conventions.
Use this agent to review, improve, or curate documentation files for Keystone UI. This includes Storybook stories in /apps/storybook/stories/ and (future) MDX documentation. The agent verifies accuracy against component source, ensures completeness of variant/state coverage, improves code examples, and maintains consistency with Keystone UI patterns. Triggers on keywords like review stories, improve documentation, curate docs, audit stories, check documentation quality, story review, docs review.
| name | design-tokens |
| description | Design token conventions for the @keystoneui/react component library. Apply when creating or editing UI components in packages/ui/. |
| globs | packages/ui/src/**/* |
| alwaysApply | false |
These are the established conventions for the @keystoneui/react component library. Follow them when creating or modifying components.
Component runtime CSS (animations, transitions, hover gating) is in packages/ui/src/base.css. Theme tokens are consumer-owned -- defined in the consumer's CSS, not in a package file. The default token values are in packages/ui/registry/default.json. Key custom tokens beyond the standard shadcn set:
--input-bg — form control background (transparent in light, input at 30% in dark). Use bg-input-bg.--popup-ring — subtle ring for popup containers (border at 10% opacity). Use ring-popup-ring.border-input — form controls (Input, Textarea, Select, NativeSelect, Checkbox, Radio, Switch, ComboboxChips, InputGroup)border-border — structural borders (AccordionItem, Button outline, Badge default, Avatar, Card)These resolve to the same color, but the semantic distinction matters for maintainability.
bg-input-bg — form control background. Handles light (transparent) and dark (input/30) automatically. Do NOT use bg-transparent dark:bg-input/30.bg-input/80 — Switch unchecked track only. Intentionally more opaque than form controls.ring-popup-ring — popup container rings (DropdownMenu, Select, Combobox, Popover popups, Card). Do NOT use ring-border/10 or ring-foreground/10.bg-border — standalone Separator component (structural, full-contrast)bg-border-muted — popup separators inside DropdownMenu, Select, Combobox, Popover (subtle, lower contrast)Two patterns exist, each for a different control type:
focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring/50
focus:ring-1 focus:ring-inset focus:ring-ring focus:border-ring
Do NOT mix these patterns. Do NOT use hybrid approaches (e.g., combining ring and outline).
A single --radius CSS variable (default 0.625rem / 10px) drives the entire radius scale. It is a consumer-owned theme token defined in :root, just like colors. The derived scale is registered in base.css via @theme inline:
--radius-sm: calc(var(--radius) - 4px) → rounded-sm (6px at default)
--radius-md: calc(var(--radius) - 2px) → rounded-md (8px at default)
--radius-lg: var(--radius) → rounded-lg (10px at default)
--radius-xl: calc(var(--radius) + 4px) → rounded-xl (14px at default)
--radius-2xl: calc(var(--radius) + 8px) → rounded-2xl (18px at default)
--radius-3xl: calc(var(--radius) + 12px) → rounded-3xl (22px at default)
--radius-4xl: calc(var(--radius) + 16px) → rounded-4xl (26px at default)
Changing --radius shifts the entire scale uniformly. Components use Tailwind utilities (rounded-sm, rounded-md, etc.) and never hardcode pixel values.
rounded-sm — small controls: Checkbox, nested InputGroupButton xs, ComboboxChiprounded-md — form inputs: Input, Textarea, NativeSelect, InputOTPSlot, DropdownMenuSubContent, popup itemsrounded-lg — containers/actions: Button, popups, TabsList, AccordionItem box, Toastrounded-xl — large surfaces: Card, Commandrounded-full — pills/circles: Badge, Avatar, Switch, pill tabsvar(--radius) usageSome components use calc(var(--radius) - Npx) for bespoke offsets that don't match a named tier. Since @theme inline does not create runtime CSS variables for --radius-sm etc., always use calc(var(--radius) ± offset) when referencing the radius token directly:
[--cell-radius:calc(var(--radius)-2px)] for cell radius in range selections[&>kbd]:rounded-[calc(var(--radius)-5px)] for nested kbd elementsdisabled: — native HTML disabled (Input, Textarea, Button, Checkbox, Radio, Select, NativeSelect, Tabs)data-disabled: — Base UI data attribute (DropdownMenuItem, ComboboxItem, SelectItem)cursor-not-allowed and opacity-50Every interactive component applies three SVG utilities together:
[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4
pointer-events-none — prevents SVGs from intercepting clicksshrink-0 — prevents SVGs from shrinking in flex layoutssize-4 with escape hatch — default size that consumers can override via size-* classesDo NOT use [&_svg]:size-4 which forces size on all SVGs with no override. Always include all three utilities together.
Import from packages/ui/src/utils.ts:
POPUP_ITEM_HEIGHT — shared height class for popup list items in DropdownMenu, Select, and ComboboxAll popup item components (DropdownMenuItem, SelectItem, ComboboxItem, and their variants) use the POPUP_ITEM_HEIGHT constant to ensure consistent sizing:
h-9 [[data-size=compact]_&]:h-8
| Size | Height | Pixels | When to use |
|---|---|---|---|
default | h-9 | 36px | Standard menus and dropdowns — comfortable touch targets |
compact | h-8 | 32px | Dense lists with many items — set size="compact" on the Content component |
The size prop is available on DropdownMenuContent, SelectContent, and ComboboxContent:
// Default (36px items)
<DropdownMenuContent>...</DropdownMenuContent>
// Compact (32px items)
<DropdownMenuContent size="compact">...</DropdownMenuContent>
The compact size activates via data-size="compact" on the content container, which the POPUP_ITEM_HEIGHT class detects using Tailwind's [[data-size=compact]_&]: descendant selector. Always use the shared constant — never hardcode h-9 or h-8 on popup items directly.
For popup container styling, animations, item heights, and separator conventions, see .cursor/rules/popup-patterns.mdc.
The Accordion uses its own custom animations (not the shared popup pattern): data-[open]:animate-accordion-down data-[closed]:animate-accordion-up.
Different transition properties for different control types:
transition-[color,box-shadow] — form controls (Input, Textarea, Select, NativeSelect, InputGroup, InputOTP)transition-[color,background-color,border-color,box-shadow] — TabsTrigger (text, bg, border, and shadow all change on active)transition-[background-color,border-color] — Switch (bg and border change on checked/invalid)transition-transform — icon rotations (Select chevron, Accordion chevron). Paired with duration-150 ease-out.transition-colors — ComboboxChipstransition-none — applied on aria-invalid states for Checkbox, Radio, Switch to prevent flash on validationDo NOT use transition-all unless the component has active:scale press feedback (see below). Always specify the exact properties that transition. Do NOT use transition-colors for controls that also transition box-shadow.
Exception: Button uses transition-all because it combines color, background, border, and transform (active:scale-[0.98]) transitions. Listing all properties individually would be fragile.
Three shadow tiers:
shadow-xs — form controls (Input, Textarea, Select, NativeSelect, ComboboxChips, InputGroup, InputOTP)shadow-lg — popup containers (SelectContent, ComboboxContent, DropdownMenuContent, PopoverContent)shadow-sm — TabsTrigger active state (default variant only)Two aria-invalid patterns, matching the focus style of the control:
aria-invalid:ring-destructive dark:aria-invalid:ring-destructive aria-invalid:border-destructive
aria-invalid:border-destructive dark:aria-invalid:border-destructive/50
aria-invalid:transition-none
aria-invalid:focus-visible:outline-destructive/50
Buttons use a ring-based invalid pattern (from shadcn) for form error indication:
aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive
When checked and invalid, both Checkbox and Radio switch to destructive styling:
aria-invalid:data-checked:bg-destructive aria-invalid:data-checked:border-destructive aria-invalid:data-checked:text-destructive-foreground
Standard heights used across components:
h-4 — Badge xsh-5 — Badge default, Kbdh-6 — Badge sm, Button xs, InputGroupButton xs, SelectScrollButtonh-8 — Button sm, TabsTrigger, InputGroupButton sm, popup items (compact)h-9 — SelectTrigger sm, popup items (standard via POPUP_ITEM_HEIGHT)h-10 — Button/Input/SelectTrigger/NativeSelect/InputOTP default, ComboboxChips min-hh-12 — Button lgUse size-* (not h-*) for square controls: Checkbox (size-4), Radio (size-4), Button icon-xs (size-6), Button icon-sm (size-8), Button icon (size-10), Button icon-lg (size-12).
data-slot Patterndata-slotEvery exported component part gets a data-slot attribute. These are stable, semantic identifiers that act as a public "parts API" for compound components. Unlike Tailwind classes (which change with styling decisions) or DOM structure (which is an implementation detail), data-slot values are intentional API that won't break when internals are refactored.
Follow [component]-[part] kebab-case naming:
data-slot="select-trigger"
data-slot="dropdown-menu-item"
data-slot="combobox-chips-input"
Root-level components use the component name alone (e.g., data-slot="card", data-slot="button").
Parent-aware layout via has-data-[slot=...] — a parent adjusts its own styles based on which children are present:
// Card removes bottom padding when a footer exists
has-data-[slot=card-footer]:pb-0
// CardHeader switches to a two-column grid when an action is present
has-data-[slot=card-action]:grid-cols-[1fr_auto]
Parent-to-child styling via *:data-[slot=...] — a parent styles a child part without the child needing to know:
// SelectTrigger truncates and lays out the value text
*:data-[slot=select-value]:line-clamp-1
*:data-[slot=select-value]:flex
*:data-[slot=select-value]:items-center
// AvatarGroup adds ring spacing to each avatar
*:data-[slot=avatar]:ring-2
*:data-[slot=avatar]:ring-background
Consumer customization — consumers can target specific parts for overrides without depending on class names or DOM nesting:
[data-slot="card-title"] { font-size: 1.25rem; }
Always assign a data-slot to every exported component part.
data-size and Group NamingSize variants are communicated via data-size attributes rather than separate class props:
default, sm, lg, compactdata-[size=sm]: selectors on the element itselfParent-child styling relationships use Tailwind's named group pattern:
group/card, group/input-group, group/tabs-list, etc.group-data-[size=sm]/card:, group-has-disabled/field:, group-data-[variant=line]/tabs-list:The group name always matches the component's data-slot value (e.g., group/card pairs with data-slot="card").
text-foreground — primary text (headings, labels, active states)text-muted-foreground — secondary text (descriptions, helper text, inactive icons, popup labels)text-muted-foreground/70 — placeholder text conventiontext-destructive — error messages and destructive variant texttext-popover-foreground — popup container text (set on the popup root)text-card-foreground — card container text (set on the card root)Do NOT use raw color values (e.g., text-gray-500) in component base styles. Always use semantic tokens.
Exception: Badge color variants intentionally use raw Tailwind colors (bg-red-500/15, text-red-700) because each variant maps to a distinct hue that cannot be represented by a single semantic token.
Buttons apply a micro-scale press animation:
active:scale-[0.98] disabled:active:scale-100
active:scale-[0.98] gives subtle tactile feedback on clickdisabled:active:scale-100 cancels the animation when disabledButton — popup items and other clickable elements do NOT have press feedbackThis is the reason Button uses transition-all (see Transition Properties above).
Tailwind v4 changed cursor: pointer to cursor: default for buttons. We handle this with explicit cursor-pointer on each interactive component rather than a global CSS base rule.
Components that need cursor-pointer:
cursor-pointer in CVA base)Components that need cursor-not-allowed:
disabled:cursor-not-allowed)data-disabled:cursor-not-allowed)Do NOT rely on browser defaults for cursor behavior. Always set it explicitly.
shrink-0Buttons include shrink-0 on the root element to prevent them from shrinking in flex containers. This is a defensive CSS practice — without it, a button inside flex could collapse when sibling content is larger.
inline-flex shrink-0 items-center justify-center ...
Note: SVGs inside buttons also have shrink-0 via the SVG Boilerplate rule. The root-level shrink-0 is separate and applies to the button element itself.
When adding a new semantic token to the library:
:root and .dark in the consumer's theme CSS (and in packages/ui/registry/default.json for the default registry style)::root {
--my-token: oklch(0.5 0.1 200);
}
.dark {
--my-token: oklch(0.3 0.1 200);
}
@theme inline block:@theme inline {
--color-my-token: var(--my-token);
}
bg-my-token text-my-token border-my-token
Follow the background / foreground naming convention described below.
We use a simple background and foreground convention for colors. The background variable is used for the background color of the component and the foreground variable is used for the text color.
The background suffix is omitted when the variable is used for the background color. Given the following CSS variables:
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
The background color of the following component will be var(--primary) and the foreground color will be var(--primary-foreground):
<div className="bg-primary text-primary-foreground">Hello</div>
This pattern applies to every color pair in the theme:
| Token | Background class | Text class |
|---|---|---|
--primary | bg-primary | text-primary-foreground |
--secondary | bg-secondary | text-secondary-foreground |
--destructive | bg-destructive | text-destructive-foreground |
--muted | bg-muted | text-muted-foreground |
--accent | bg-accent | text-accent-foreground |
--card | bg-card | text-card-foreground |
--popover | bg-popover | text-popover-foreground |
When adding new color pairs, always follow this convention: --name for background, --name-foreground for text.