Design Craft

SkillMedia

Constraint + process skill for UI that reads "designed", not "AI-generated". Mandatory two-pass process (token plan grounded in the subject's world + ONE signature element, then self-critique) whose output is the project's root DESIGN.md — the design contract re-read on every UI generation. Navigation selection matrix, logo rule (vary expression not placement), icon discipline (one library, labeled), typography pairing recipe, dominant+accent color, copy rules — on top of a non-negotiable usability floor (focus rings, empty/loading/error states, WCAG AA, <=200ms). Ships with AVOID-LIST.md (versioned AI-tell list) and DESIGN-TEMPLATE.md (DESIGN.md template). Triggers: build UI, new component, design review, polish UI, "looks like AI made it", design system, style guide, /toh-vibe, /toh-ui, /toh-design.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Design Craft skill

What this skill tells your AI

The instructions your AI receives, as published by wasintoh/toh-framework in src/skills/design-craft/SKILL.md and read by ahel’s review.

Two layers — never confuse them:

LayerWhatWhere decided
Creative layerPalette, type, nav, signature element — different per projectRoot DESIGN.md, generated from DESIGN-TEMPLATE.md
Usability floorEvidence-backed rules that never bendBottom of this file

Sibling files (same folder):

  • AVOID-LIST.md — versioned AI-tell list. Consult during identity generation and every design review.
  • DESIGN-TEMPLATE.md — template + filled example for the generated per-project root DESIGN.md.

The contract: root DESIGN.md is the project's design identity. Re-read it before every UI generation — never work from memory of it. If it doesn't exist and UI is about to be written, generate it first (two-pass process below → template). For UI projects it is the first plan task (design-reviewer authors it before any UI work).


Meta-Rule

Defaults are fine when chosen for the brief. Never when inherited.

Inter, indigo-500, rounded-lg, a 3-card feature row — each is legitimate when the brief calls for it, and slop when it appears because it's the training-data median. Every value in the UI must trace to a DESIGN.md token chosen for THIS project.


Two-Pass Process (mandatory before any UI code)

Pass A — compact token plan. Ground it in the subject's actual world — materials, place, era, mood. (A restaurant supply marketplace lives among stainless steel, wet markets, delivery bikes — not "warm and friendly".)

  • 4-6 named hex colors incl. hue-biased neutrals (never pure gray)
  • Display / body / utility typeface roles + weights
  • One-sentence layout concept
  • ONE signature element — the single bold, memorable move (distinctive hero treatment, unusual data display, strong typographic motif). One. Everything else stays quiet.

Pass B — self-critique. Ask: "Would I produce this exact plan for any similar brief?" If yes → revise and state what changed. Check the plan against AVOID-LIST.md.

Only after Pass B survives: write the plan into DESIGN.md and code — deriving every color/type/radius/motion value from it. No raw Tailwind palette colors (indigo-500) that bypass the tokens.


Navigation Selection Matrix

Project typeNavigation pattern
Marketing / landingVisible top bar, 4-7 text links
Dashboard / appLeft sidebar with labeled icons
Content / docsTop bar + secondary sidebar / table of contents
E-commerceTop bar + mega menu for categories
Peer views (same-level sections)Tabs

Hard rules, all patterns:

  1. Never a hamburger on desktop — hiding nav halves discoverability. Visible links at md+ always.
  2. Nav items are real links (<a> / <Link>), never divs with onClick.
  3. URL reflects state — views are shareable and back-button-safe.

Logo Rule — vary expression, not placement

  • Top-left, clickable to home — always, for apps and sites (NNGroup: returning home is ~6x harder with a centered logo).
  • Brand variety comes from wordmark treatment (typeface, weight, spacing), a motif, or an accent system — not from moving the logo.
  • Centered logo ONLY for editorial/luxury briefs that explicitly call for it, and only with a compensating home affordance (visible "Home" link).

Icon Discipline

  • Exactly ONE library per project, declared in DESIGN.md: Lucide / Phosphor / Heroicons / Tabler — plus stroke weight and size grid (e.g. 16/20/24).
  • Every icon gets a text label — except true universals (search, close ×, chevron).
  • Icon-only buttons require aria-label.
  • Never emoji as UI icons.
  • "No icons" is a valid choice — often the stronger one for editorial identities.
  • Icon-tile-above-heading cards are the #1 AI tell (see AVOID-LIST). Icons support content; they don't headline it.

Typography Pairing Recipe

Formula: characterful display + complementary body + optional mono/data face.

  • Inter / Geist are body/utility faces ONLY — never the personality carrier.
  • Use weight extremes: pair 100-200 against 800-900 instead of everything at 400-600.
  • Hero: 3x+ size jump from body. Scale ratio >= 1.25. Body measure 65-75ch. tabular-nums on any column of numbers.
  • Category starting points — rotate; never converge across projects:
    • Editorial / food / lifestyle → Fraunces, Newsreader
    • Technical / data → IBM Plex family
    • Startup / product → Cabinet Grotesk, Satoshi These are prompts, not defaults. Pick for the brief; if the last project used it, pick differently.
  • Thai: IBM Plex Sans Thai / Noto Sans Thai / Anuphan / Sarabun. Next.js: load via next/font (self-host, no layout shift) — never a raw Google Fonts <link>.

Type scale (Tailwind)

Display / Hero:   text-4xl / text-5xl+  display face   tracking-tight
Page Title:       text-2xl  font-semibold              (24px)
Section Title:    text-lg   font-medium                (18px)
Card Title:       text-base font-medium                (16px)
Body:             text-sm   font-normal                (14px, line-height 1.5-1.6)
Small / Caption:  text-xs   text-muted-foreground      (12px)

Rules: headings never font-normal · body regular, never bold-everything · max 3 sizes per component · no long ALL-CAPS runs.


Color

  • Dominant + sharp accent — commit to a dominant character, cut it with one sharp accent. Timid even distribution reads generic.
  • Semantic status colors (good / warn / critical) are separate tokens from the brand accent — never reuse the accent for status.
  • Design light AND dark token-first; both stated in DESIGN.md. Dark mode is its own palette, not inverted gray.
  • Palette sourced from the subject's world or a named cultural aesthetic — name the source in DESIGN.md.
  • Neutrals are hue-biased toward the palette, never pure #808080. No pure #000 text. Contrast passes WCAG AA in both modes.
  • Wire tokens as CSS custom properties (Tailwind 4 @theme / shadcn variables): --background --foreground --muted-foreground --border --primary --destructive map to DESIGN.md tokens.
  • No gradients on primary buttons — solid reads intentional.

Copy Rules

  • Active voice. No buzzwords (streamline, empower, supercharge, unleash).
  • Buttons say exactly what happens: "Save invoice", not "Continue" / "Submit".
  • Errors state the fix: "Card declined — try another card", never an apology or a stack dump.
  • Declare a case system in DESIGN.md (e.g. sentence case everywhere) and keep it.
  • Real content stance: realistic domain data, no Lorem ipsum, no "Welcome back, User! 👋". Copy follows the project language (Thai = real Thai copy, not stiff translation).

Density & Spacing

Layout reflects the real work, not an empty template:

  • Dashboard / admin / data → dense: tables, stat rows, real data. Not "3 floating cards + giant hero".
  • Marketing / landing → narrative sections with substance; hero allowed but earns its space.
  • Form / settings → vertical flow, related fields grouped.
  • Mix composition: table / list / stat / section — never Card → Card → Card. Don't center everything.

Spacing scale (Tailwind — use consistently)

2  (8px)   inline / icon gap
3  (12px)  gap within a component
4  (16px)  standard gap between elements
6  (24px)  between components / card padding
8  (32px)  between sections
12 (48px)  major page sections

Same level = same gap. No random p-3 here, p-5 there.

Border-radius — vary by element, cap declared in DESIGN.md

Inputs / small buttons:  rounded-md   (6px)
Cards / dialogs:         rounded-lg / rounded-xl  (8-12px)
Avatars / pills / badge: rounded-full
Cap:                     12px (rounded-xl) for general containers

Component Patterns (structural — visual character comes from DESIGN.md tokens)

React 19: ref is a normal prop — no forwardRef.

// Card — thin border + light shadow; no gradient border, no glassmorphism, no rounded-3xl
<Card className="bg-card border shadow-sm">
  <CardContent className="p-6">{/* ... */}</CardContent>
</Card>

// Buttons — one primary per view; no gradient / 3D
<Button>Save invoice</Button>
<Button variant="outline">Cancel</Button>
<Button variant="destructive">Delete</Button>
<Button variant="ghost" size="icon" aria-label="Settings"><Settings className="size-4" /></Button>

// Form (React 19 Actions) — label above input, inline errors
<form action={formAction} className="space-y-4">
  <div className="space-y-2">
    <Label htmlFor="name">Name</Label>
    <Input id="name" name="name" />
    {state?.errors?.name && <p className="text-sm text-destructive">{state.errors.name}</p>}
  </div>
  <SubmitButton /> {/* useFormStatus() → disabled + pending label */}
</form>

// Table — muted header, hover rows, right-aligned actions; no zebra stripes, no centered content
<TableRow className="hover:bg-muted/50">
  <TableCell>John Smith</TableCell>
  <TableCell><Badge>Active</Badge></TableCell>
  <TableCell className="text-right"><Button variant="ghost" size="sm">Edit</Button></TableCell>
</TableRow>

Motion

Motion gives feedback; it is not decoration. Fast (150-200ms), subtle, ease-out.

<div className="transition-colors hover:bg-muted" />
<Card className="transition-shadow hover:shadow-md" />
// Framer Motion enter: { opacity: 0, y: 8 } → { opacity: 1, y: 0 }, duration 0.2, easeOut
// List stagger: 0.05 max · press: whileTap={{ scale: 0.98 }}

Hover ~150ms · general ~200ms · cap 200ms · no bounce/elastic, no dramatic entrances, no animating everything. Respect prefers-reduced-motion.


NON-NEGOTIABLE USABILITY FLOOR

The creative layer never overrides these:

RuleDetail
Logo top-left, clickable to homeAlways for apps/sites (NNGroup ~6x evidence)
No hamburger on desktopVisible nav links at md+
Icons labeled, one libraryaria-label on icon-only buttons; never emoji as icons
Focus statesfocus-visible:ring-2 ring-ring on every interactive element
Interactions <= 200msease-out; no bounce
WCAG AA contrastLight and dark
Empty statesReal sentence + next action — never bare "No data"
Loading statesSkeletons matching the real layout — not a centered spinner
Error statesInline: what happened + what to do next
Hover / active feedbackEvery button/row (hover:bg-muted, active:scale-[0.98])
Disabled / pendingSubmit buttons show working state (useActionState / useFormStatus)

Review Checklist

  • Root DESIGN.md exists and was re-read for this generation
  • Every color/typeface/radius/motion value traces to a DESIGN.md token
  • Signature element present on the flagship view; everything else quiet
  • Zero AVOID-LIST patterns (scan AVOID-LIST.md groups)
  • Nav matches the matrix · logo top-left + clickable · URL reflects state
  • One icon library, labeled · type pairing not Inter-as-display
  • Usability floor: focus/empty/loading/error/hover/pending all present, WCAG AA
  • Final: would a user tell AI made it? → must be no. Would two different briefs get this same design? → must be no.

Design Craft — one identity per project, chosen on purpose, on top of a floor that never bends.

Signals

GitHub stars
96
Forks
19
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
design-craft
Source
github.com/wasintoh/toh-framework