| name | ui-design-shadcn |
| description | Use this skill when designing and implementing new UI (pages, forms, lists, tables, dialogs, empty states) with shadcn/ui + Tailwind v4. |
UI Design Skill
Use this skill when you need to design (visual structure + UX) and then implement UI that fits your project’s existing look and feel.
This is not a “make it pretty” free-for-all. It’s about being consistent with:
- shadcn/ui primitives (commonly in
components/ui/*)
- Tailwind v4 utility conventions
- Your app’s layout/shell components
- Next.js App Router server-first architecture + i18n (no hardcoded strings)
What “on-brand” looks like here
Design principles
- Structure first: clear heading hierarchy, predictable spacing, obvious primary action.
- Comfortable density: prefer whitespace and scannability over cramped controls.
- Surfaces over lines: use Cards and subtle borders; avoid heavy dividers.
- One interactive island: pages are Server Components; isolate interactivity into small Client Components.
- Accessible by default: keyboard-first flows, visible focus, proper labels.
The default visual language (practical rules)
- Spacing: default to
gap-4, p-4/6/8, space-y-4.
- Radius: follow your design system’s radius rules. Some projects standardize on
rounded-none; others use sm/md/lg. Be consistent across components.
- Typography:
- Body:
text-sm is the default.
- Section title:
text-base font-semibold.
- Page title:
text-xl font-semibold (sometimes text-2xl).
- Muted meta:
text-sm text-muted-foreground.
- Color: do not hardcode colors. Rely on theme tokens and existing component styles.
- Icons: if you add icons, ensure they’re decorative unless they convey meaning; always include accessible labels for icon-only buttons.
Page composition patterns
Dashboard page scaffold
Prefer this mental model for most pages:
- Page header
- Title (left)
- Primary action(s) (right)
- Optional secondary filter row below
- Primary content
- One or more sections, usually Cards
- States
- Empty state (before data exists)
- Loading skeleton/placeholder
- Error state
References:
- Shell: your dashboard/layout components
- Empty state: your shared empty-state component(s)
Cards as sections
Use Card to group content. Inside a Card:
- Header row: title + small actions
- Body: form fields, list, or table-like layout
- Footer: primary action row (optional)
Use Separator sparingly (prefer spacing).
Component selection (use existing building blocks)
Default to primitives in components/ui/*:
- Buttons:
components/ui/button.tsx
- Cards:
components/ui/card.tsx
- Inputs/labels:
components/ui/input.tsx, components/ui/label.tsx
- Select/combobox:
components/ui/select.tsx, components/ui/combobox.tsx
- Dialogs:
components/ui/alert-dialog.tsx
- Dropdown menus:
components/ui/dropdown-menu.tsx
- Feedback:
components/ui/badge.tsx, (and any existing Alert component if present)
Rules:
- Prefer existing components over custom HTML+classes.
- If you need new UI primitives, add them to
components/ui/ only when truly reusable.
- For per-feature UI, prefer
app/[locale]/(dashboard)/.../_components/*.
Forms (our standard UX)
Architecture
- Use Server Actions for mutations.
- In Client Components, wire with
<form action={action}> and use useActionState / useFormStatus for pending UI.
- Validation is primarily server-side (Zod). Client validation is optional and only for immediate UX hints.
Layout
- Single-column forms by default.
- Use two columns only for short, related fields (e.g., first/last name, start/end date) and keep it responsive.
Field rules
- Every input has a visible
Label.
- Show errors near the field; keep wording user-friendly and translated.
- Use placeholders sparingly (never as the only label).
References:
- Helpers: your form helpers (if present)
- Action results: your shared action result union type (if you use one)
Lists and “table-ish” UI
We often don’t need heavy tables.
Prefer:
- A Card containing a vertical list of rows
- Row = left (name + meta) + right (amount/status/actions)
Guidelines:
- Make the primary click target obvious (row click or explicit action button, not both unless necessary).
- Keep secondary actions in a
DropdownMenu to reduce noise.
- For destructive actions, use
AlertDialog.
Empty / loading / error states
Empty state
Use EmptyStateCard (or replicate its layout):
- Friendly title
- 1–2 lines explaining what’s missing
- Primary CTA button
Loading
- Prefer segment-level
loading.tsx where appropriate.
- Inside a page, consider a lightweight placeholder instead of spinners.
Error
- Prefer segment-level
error.tsx for user-facing errors.
- Avoid exposing raw errors to the user.
Copy & i18n (non-negotiable)
- Do not hardcode UI strings.
- Use next-intl translations from
messages/en.json and messages/de.json.
- Keep microcopy short, action-oriented, and consistent (e.g., “Create client”, “Save changes”, “Archive”).
References:
- i18n routing:
i18n/routing.ts
- Messages:
messages/en.json, messages/de.json
Formatting and data display
- Currency: format at the edge; money is integer cents + currency.
- Dates/times: store UTC; format for display using existing helpers.
References:
- Formatting helpers: your formatting utilities (if present)
Accessibility checklist
Implementation checklist (before you ship)
Cost & performance by design (don’t bolt it on)
- Prefer Server Components for page composition; only carve out Client Components for true interactivity.
- Use
<Suspense> boundaries to stream “slow but non-blocking” sections.
- Avoid UI patterns that force frequent broad revalidation (e.g., overusing refreshes after every minor mutation).
- Keep list/table rows cheap: avoid per-row DB fetches (N+1) and avoid per-row client-only widgets unless necessary.
Reference: nextjs-cost-performance skill.
References (project anchors)
- UI primitives: commonly
components/ui/*
- Layout/shell components: wherever your project keeps them
- Utilities/formatting: your shared helper modules (if present)
- i18n: your i18n modules + message catalogs (if present)
- App routes: your App Router routes