| 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 in Freelancerino. |
UI Design Skill (Freelancerino)
Use this skill when you need to design (visual structure + UX) and then implement UI that fits Freelancerino’s existing look and feel.
This is not a “make it pretty” free-for-all. It’s about being consistent with:
- shadcn/ui primitives in
components/ui/*
- Tailwind v4 utility conventions
- Dashboard shells in
components/dashboard/*
- Next.js 16 server-first architecture + next-intl (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: no rounded
rounded for cards and inputs; avoid mixed radii.
- 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:
components/dashboard/header.tsx, components/dashboard/sidebar.tsx
- Empty state:
components/dashboard/empty-state-card.tsx
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:
components/ui/field.tsx, lib/forms/form-data.ts
- Action results:
lib/actions/action-result.ts
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:
lib/format.ts
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: .github/skills/nextjs-cost-performance/SKILL.md
References (project anchors)
- UI primitives:
components/ui/*
- Dashboard components:
components/dashboard/*
- Utilities:
lib/utils.ts
- Formatting:
lib/format.ts
- i18n:
i18n/*, messages/*
- App routes:
app/[locale]/(dashboard)/*