Skip to content
Back to skills

Fine Design

ASecurity

Use when building or restyling React UI in a project that uses Fine (fine-design, @fine/ components, theme-ledger, theme-folio, theme-poster), or when asked to install or use Fine components. Fine is a shadcn registry on Base UI, Tailwind v4 and Motion. Behavior is the same everywhere; appearance is a preset (Ledger, Folio, Poster).

  • 4 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added October 26, 2026
designgoshellreactexpressspringgitapi

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned October 26, 2026

npx -y skills add anon5376/fine-design --skill fine-design --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Fine Design?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Fine Design
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anon5376-fine-design/badge)](https://www.skillsdirectory.com/skills/anon5376-fine-design)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: fine-design
description: Use when building or restyling React UI in a project that uses Fine (fine-design, @fine/ components, theme-ledger, theme-folio, theme-poster), or when asked to install or use Fine components. Fine is a shadcn registry on Base UI, Tailwind v4 and Motion. Behavior is the same everywhere; appearance is a preset (Ledger, Folio, Poster).
---

# Fine

React design system as a shadcn registry. 42 components, 7 blocks, 4 lib files, `fine-base` and 4 theme items. Site: https://fine-design.qeqx.chatgpt.site

Behavior is universal: Base UI roles, keyboard and focus, validation, reduced motion, the motion tiers, the accessibility contracts, and styling through tokens only. Appearance is a preset: Ledger, Folio or Poster, each a set of token values.

## Direction first

Before building a new site, page or screen, settle the direction. Read the project's design doc if it has one (`DESIGN.md` or similar). Otherwise decide these from the brief and write them into a short `DESIGN.md` first: audience, content, typography, palette, composition, imagery, interaction character. Then pick the closest preset, or derive a new one (see Making a preset), and build with Fine components.

Use Ledger when the user asks for it, or when the job is a dense data or operations tool. A small edit inside an existing screen follows that screen's direction; skip this step for it.

| Preset | Direction | Take it for |
|---|---|---|
| Ledger | Achromatic, dense, flat at rest, ink chrome with one cobalt accent | Dashboards, admin and finance tools |
| Folio | Editorial, set for reading: warm paper, serif titles, one quiet aubergine accent | Docs, publications, content-led pages |
| Poster | Loud, large type, strong contrast: a flat hot magenta for the main action and the accent, hard shadows | Launches, campaigns, marketing pages |

The table states each preset's intent. The values are in the theme item (`npx shadcn@latest view @fine/theme-<name>`) and in `scripts/presets/<name>.mjs` in the Fine repo.

## Setup

Needs React 19, Tailwind v4, and a shadcn `components.json` with the default aliases (`@/components`, `@/components/ui`, `@/lib`). Run `npx shadcn@latest init` first if the project has none.

Install a theme once, then components. A component never re-skins a project.

1. Register the namespace in `components.json`:
   ```json
   "registries": { "@fine": "https://fine-design.qeqx.chatgpt.site/r/{name}.json" }
   ```
2. Install one theme: `npx shadcn@latest add @fine/theme-ledger` (or `theme-folio`, `theme-poster`). It writes values only (colors, fonts, weights, radii, type scale, control sizes, padding, border width, shadows) to `:root`, `.light`, `.dark` and the OS dark query. A second theme replaces the first; put overrides in a later rule. `fine-theme` is an alias of `theme-ledger`. Tailwind's palette stays.
3. Add components: `npx shadcn@latest add @fine/button @fine/dialog`. Each depends on `fine-base` (the `@theme inline` mappings, the `figures`, `figure-fit`, `focus-ring` and `border-line` utilities, keyframes, motion scalars, and Ledger defaults in `@layer base`) plus other items and npm packages. Your `:root` and the installed theme beat those defaults, so adding a component later does not change the look. With no theme installed, components render in Ledger.
4. After `shadcn init`, add `--overwrite` once: init wrote its own `components/ui/button.tsx` and `lib/utils.ts`, and Fine's versions replace them (icon-button and dialog build on Fine's button API). Fine's `cn` knows the preset utilities, so `h-control-md` and `h-12` merge.
5. Load the fonts in the theme item's `meta.fonts` with next/font, one CSS variable each, passing an entry's `axes` or `weights` (as `axes` and `weight`) when it lists them: Folio's Newsreader and Poster's Bricolage Grotesque list `opsz`, and load as a static cut without it. Ledger needs Geist as `--font-geist-sans` and `--font-geist-mono`.
6. App root: `<MotionConfig reducedMotion="user">` and one `<Toaster />`. `TooltipProvider` is optional.

Done when the page renders in the chosen preset's colors and type and the dev server logs no import errors.

## Discovery

Read https://fine-design.qeqx.chatgpt.site/llms.txt for the per-item list (description, docs URL, registry URL). It also has the conventions below in short form.

- `npx shadcn@latest search @fine`, then `npx shadcn@latest view @fine/<name>` for the source and files of one item. Fine items list their exports and variants in `meta`.
- The shadcn MCP server (`npx shadcn@latest mcp`) exposes the same search, view and add to agents. This plugin starts it; elsewhere, add it to the agent's MCP config.
- Read an item's exports and variants before guessing a prop.

## Rules agents get wrong

**Tokens.** Use semantic and preset utilities only. Surfaces and text: `bg-background`, `bg-surface`, `bg-muted`, `text-foreground`, `text-muted-foreground`, `border-border`, `border-input`. Fills: `bg-primary`, `bg-secondary`, and `success`, `warning`, `danger`, `info` (each with `-foreground`, `-subtle`, `-subtle-foreground`). Shape and type: `rounded-control`, `rounded-container`, `rounded-small`, `rounded-pill`; `h-control-sm|md|lg`; `px-pad-control-*`, `px-pad-field-*`, `p-pad-container-*`; `border-line` and its directional forms; `font-label`, `font-title`, `font-strong`, `font-display`; `shadow-raised` for resting surfaces, `shadow-control` for a control's own lift (the selected tab) and `shadow-overlay` for popups. A raw hex, a palette step (`bg-blue-500`), a fixed height like `h-9` on a control, or a literal `rounded-md` is a bug even where Tailwind's palette still exists: it breaks when the preset changes.

**Focus and dark mode.** `focus-visible:focus-ring` on anything focusable. Dark mode is `.dark` or `.light` on `<html>` or any subtree; with neither, the OS decides. `dark:` follows the same rule. Every preset defines both modes.

**Figures.** Money, counts, IDs and addresses take the `figures` utility (the preset's figure face with tabular digits). A stat figure in a card also takes `figure-fit`, inside a parent that is `@container` and sets the size (`text-xl`): the figure keeps that size when it fits and shrinks when the card or the face is too narrow. Set `--figure-chars` on the grid to the longest figure's length (default 12).

**Accent.** `accent` marks emphasis on data. How many a screen carries is the preset's call. Ledger allows one, on the main data element, never on chrome. Every preset keeps its accent (and a chromatic primary) 40 degrees or more of hue from `danger`, so an accent never reads as an error.

**Copy.** Write what the user needs to act: the label, the value, the action. A form shows a hint when an input has rules or a format (password length, date pattern): `FieldDescription` inside `Field`, stating the rule and nothing else. Whether a screen also has taglines or secondary labels follows its direction.

## Ledger

Ledger's decided look, for Ledger and nothing else: ink chrome (primary, focus ring), grey secondary, cobalt accent on a screen's main data element only, Geist plus `figures`, 6px and 8px radii, flat at rest. No taglines, secondary labels or explanatory helper text, except a `FieldDescription` on an input with a rule or format. No gradients, neon, decorative color or alternative typefaces.

## Making a preset

1. Copy `scripts/presets/ledger.mjs` to `scripts/presets/<name>.mjs` in the Fine repo. `name` equals the file name; every key is required (palette, semantic light and dark, fonts, weights, text, radius, borderWidth, control, padding, shadows); `fontOptions` is optional.
2. Write each non-contract font as `var(--font-<id>, "<Family>")` and add its `next/font/google` loader (`variable: "--font-<id>"`, `preload: false`) to `site/presets.ts`. If a face needs `axes` or a fixed `weights` list, declare it in `fontOptions` under its variable and pass the same `axes` or `weight` in the loader; the registry build throws when a font has no loader or the options differ.
3. Run `npm run tokens`. Every pair must pass: text 4.5:1, input and ring borders 3:1, accent 3:1 on background and surface, accent and a chromatic primary 40 degrees or more from danger in hue, all colors inside sRGB. Give the accent a ramp of its own in `palette.hues` if it is too close to red. Fix the values, not the gate.
4. `npm run build` derives `theme-<name>`; `npm run verify:install` proves it installs and renders.

A preset cannot change roles, keyboard paths, validation, motion tiers or the focus ring.

## Variant API

- `variant` and `size` props via cva; defaults live in each file's `defaultVariants`. Button variants: primary, secondary, outline, ghost, danger; sizes sm, md, lg. `loading` keeps the label width and blocks clicks.
- Each `<name>Variants()` export styles another element: `<a className={buttonVariants({ variant: "outline" })}>`. `render={<a href="/x" />}` swaps the element. Merge classes with `cn` from `@/lib/utils`.
- Badge and Alert share neutral plus the four statuses. IconButton requires `label`.

## Which component

| Job | Use | Not |
|---|---|---|
| Blocking decision or short form | Dialog | Drawer |
| Secondary panel, filters, mobile nav | Drawer | Dialog |
| Anchored detail or small form | Popover | Dialog |
| One value, short list | Select | Combobox |
| One value, long list with search | Combobox | Select |
| Actions on an item | DropdownMenu | Select |
| Global actions by keyboard (Meta+K) | CommandPalette | DropdownMenu |
| Persistent message in the page | Alert | Toast |
| Transient confirmation | `toast.add({ title, type })` | Alert |
| Status of a record | Badge | Tag |
| Chosen value or filter, removable | Tag | Badge |
| Same-page panels | Tabs | Sidebar |
| Site or app navigation | Navbar, Sidebar, Breadcrumb, Pagination | Tabs |
| Form with validation | Form, Field, FieldLabel, FieldError | bare Input |
| Rule or format for an input | FieldDescription inside Field | Placeholder text, Tooltip |

Blocks (`auth-form`, `hero`, `feature-grid`, `pricing`, `faq`, `footer`, `dashboard-shell`) compose `components/ui` only. Install one as a start, then edit it.

## Motion

Static is the default. Add motion only when a component already ships it, a source animation maps onto a tier, or it is the one change the user needs to notice. Alert, Badge, the Navbar wordmark, a Tag outside `TagList`, and slider drag and key steps stay static: a state change there is read from the new label, and animating them is a regression.

Pick the tier by what moves; the bigger the thing, the slower the tier.

| Tier | Enter | Exit | Use |
|---|---|---|---|
| fast | 80ms | 60ms | hover, focus, check and radio marks, tooltips, hover highlight, avatar image fade, tag removal exit |
| moderate | 160ms | 120ms | select, combobox, menu and popover enters, drawers, tab and pagination indicators, switch thumb, rolling digits, tag sibling glide, slider track-press glide |
| slow | 240ms, bounce 0.12 | 160ms | dialogs, command palette |
| expressive | 420ms visual, bounce 0.28 | 160ms | feedback arriving: toasts and the hero slips. The one overshoot |

- Motion JS: enter on `spring.<tier>`, exit on `spring.<tier>.exit` with `easing.exit`, both from `@/lib/motion`. CSS: `duration-(--duration-<tier>)` with `ease-spring-<tier>` to enter; `duration-(--duration-<tier>-exit)` with `ease-exit` to leave. Use these tokens in place of invented durations or bounce values.
- Lists, menus and nav share one hover highlight: `useFluidHover` and `<FluidHighlight>` from `@/lib/fluid-hover`. Popups use `popupSurface`, `popupMotion`, `popupItem` from `@/lib/popup`.
- Animate transform and opacity. Width moves only where size is the information (progress fill, switch thumb, highlight, a figure gaining a digit, slider position).
- Fine's own (original): `AnimatedNumber` digit roll (also the pricing price), `TagList` removal exit and sibling glide, `Slider` track-press glide, `Avatar` image fade. Use `AnimatedNumber` for a figure that changes while on screen; wrap removable `Tag`s in `TagList`. Never add loops or page-load entrances.
- Reduced motion drops travel and keeps fades: `MotionConfig reducedMotion="user"` for Motion, `motion-reduce:` for CSS.

## Accessibility contracts

- Base UI supplies roles, keyboard and focus. Keep its parts; re-implementing them breaks the tested paths (dialog focus trap and return, menu arrows, tabs Home/End, combobox label focus).
- Combobox goes inside `Field` with a `FieldLabel` before it; the label focuses the input.
- `FieldDescription` goes between the control and `FieldError`; Base UI links it to the input through `aria-describedby`, so the hint is read with the label.
- Mount `<Toaster />` once, then call `toast.add({ title, type })` from anywhere.
- Icon-only controls take `label` (IconButton). Danger Alert uses `role="alert"`, the others `role="status"`.
- Every preset keeps text pairs at 4.5:1 and input borders at 3:1; take colors from tokens and the contrast holds.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.