agentleFS
aemerson-commitsaemerson-commits/shareable-skills/skills/frontend-design/SKILL.md

frontend-design

Skill1 starsChanged 8 months agoMITView on GitHub

Design system guide — CSS tokens, Tailwind v4, shadcn/ui components, theming, and UI conventions for consistent dark-themed dashboard interfaces.

187 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use instantly-campaign, albert-dm and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • yawningwhaleAISkill

    instantly-campaign

    Create a new Instantly cold-email campaign in the proven 5-email structure of the user's top-performing campaign (~16% reply rate). Use when the user wants to launch a new outbound campaign, write a cold email sequence, create an Instantly campaign, draft outreach for a new service or niche, or set up a sales sequence.

    Also in 6 projects that use this

  • yawningwhaleAISkill

    albert-dm

    Reply to DMs and messages the way Albert would. Use when the user asks "how would Albert reply to this", pastes a conversation and wants an Albert-style response, or asks you to draft/write a DM in Albert's voice. Albert sells AI voice agents / automations to local service businesses (Go High Level, lead follow-up, bookings).

    Also in 5 projects that use this

  • naman10parikhSkill

    ecosystem-reference

    Quick-access directory of Claude Code ecosystem repos, skills, plugins, MCPs, and patterns. Use when improving the harness.

    Also in 5 projects that use this

  • naman10parikhSkill

    fractal-delegation

    Also in 5 projects that use this

  • julianoczkowskiSkill

    brief-to-tasks

    Break a design brief into an ordered checklist of independently buildable tasks using vertical slices. Saves as a markdown checklist. Use when user wants to break down work, create tasks from a brief, plan implementation order, or mentions "tasks" or "breakdown".

    Also in 7 projects that use this

  • SeifBenayedSkill

    file-reading

    Use this skill when a file has been uploaded but its content is NOT in your context — only its path at /mnt/user-data/uploads/ is listed in an uploaded_files block. This skill is a router: it tells you which tool to use for each file type (pdf, docx, xlsx, csv, json, images, archives, ebooks) so you read the right amount the right way instead of blindly running cat on a binary. Triggers: any mention of /mnt/user-data/uploads/, an uploaded_files section, a file_path tag, or a user asking about an uploaded file you have not yet read. Do NOT use this skill if the file content is already visible in your context inside a documents block — you already have it.

    Also in 6 projects that use this

  • +6 more from naman10parikh

The file

Show its rules and text

We split this skill into the rules it gives an agent

22 rules. 0 are common, 12 are distinctive, and 10 are very specific to this project.

  • 0

    Common

    Most of the 181 edited versions we read keep these.

  • 12

    Distinctive

    This author's own choices. Worth a look.

  • 10

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    All colors, spacing, and visual properties use CSS custom properties.

    line 23
  2. Distinctive:

    When migrating a component to Tailwind, remove its CSS rules to avoid conflicts

    line 90
  3. Very specific:

    Add theme object to THEMES array in ThemeToggle.jsx

    line 163
  4. Very specific:

    Add [data-theme="your-theme"] selector block in theme.css with all token overrides

    line 164
  5. Very specific:

    Use a consistent KPICard component for metric cards across all projects:

    line 171
  6. Distinctive:

    Use shared tooltip styles if your project exports them:

    line 177
  7. Distinctive:

    If using a ratcheted lint gate: existing charts are grandfathered.

    line 219
  8. Distinctive:

    Reference legends (a key to a table, not a chart): When a legend explains what the badges, marks or colours in a table mean, build it from …

    line 224
  9. Very specific:

    Bundle split: Keep Recharts in manualChunks (vite.config.js)

    line 231
  10. Distinctive:

    Use a shared modal component with:

    line 243
  11. Distinctive:

    When rewriting a view to use Tailwind + shadcn:

    line 272
  12. Distinctive:

    Use shadcn components for structure (Dialog, Table, Card, Tabs, Button)

    line 274
  13. Distinctive:

    Use Tailwind utilities for layout and spacing

    line 275
  14. Very specific:

    Remove the old CSS rules from App.css

    line 276
  15. Distinctive:

    Keep domain-semantic styles (status colors, chart configs) in CSS or constants

    line 277
  16. Distinctive:

    Grep the target for hardcoded drift: before editing:

    line 284
  17. Very specific:

    After /design-showcase and the user picks a variant, porting it into the real component has a specific failure mode worth calling out.

    line 320
  18. Very specific:

    Detection: before accepting an agent's port commit, check git log <worktree-base>..HEAD -- <target-file>.

    line 324
  19. Very specific:

    If the CSS additions are purely additive (new scoped classes) — cherry-pick with git checkout --ours <target.jsx> to keep your current JSX …

    line 327
  20. Distinctive:

    Prefer additive edits over reordering: If the showcase variant spec includes "reorder sections", save that for a separate follow-up — it's …

    line 331
  21. Very specific:

    getDueDateCategory "this week" filter: must include late + today + tomorrow + thisWeek (not just thisWeek)

    line 339
  22. Very specific:

    CSS pattern audit before adding styles: Before adding @keyframes or new CSS classes to a large App.css, grep existing patterns first to …

    line 344

“Common” is measured: kept by at least 6 in 10 of the 181 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Frontend Design Guide

Automatically applies when working on frontend UI/UX tasks. Creates distinctive, production-grade interfaces with consistent styling.

Design System Stack

LayerTechnologyPurpose
Tokensshared/theme.css (CSS custom properties)Color, spacing, typography, radius, shadows, z-index
Themesshared/ThemeToggle.jsx (multiple presets)Theme switching via data-theme attribute on <html>
Animationsshared/animations.cssShared keyframes + reduced-motion support
UtilitiesTailwind CSS v4 (@tailwindcss/vite)Utility-first classes, no preflight (coexists with vanilla CSS)
Componentsshadcn/uiAccessible primitives: Button, Dialog, Input, Badge, Table, etc.
Existing CSSPer-project App.cssUnlayered vanilla CSS (wins specificity over Tailwind @layer)

Design Tokens (shared/theme.css)

All colors, spacing, and visual properties use CSS custom properties. Never hardcode hex values — use tokens.

Color Tokens

TokenDark ValueUsage
--bg-app#111827Page/app background
--bg-surface#1F2937Card/panel backgrounds
--bg-elevated#374151Hover states, secondary bg
--bg-inset#0F172AInset/recessed areas
--text-primary#F9FAFBMain content text
--text-secondary#9CA3AFLabels, descriptions
--text-muted#6B7280Hints, timestamps
--accent#3B82F6Primary actions, links
--accent-hover#2563EBHover on accent
--accent-muted#60A5FALight accent variant
--status-error#EF4444Errors, late items
--status-warning#F59E0BWarnings, approaching due
--status-success#10B981Success, on-time
--status-info#3B82F6Informational
--border-default#374151Standard borders
--border-subtle#1F2937Subtle dividers

Spacing Scale

--space-xs (0.25rem) through --space-3xl (3rem). Use in CSS: padding: var(--space-md).

Typography Scale

--text-xs (0.75rem) through --text-3xl (1.875rem). Use: font-size: var(--text-sm).

Radius, Shadows, Z-Index

  • Radius: --radius-sm through --radius-full
  • Shadows: --shadow-sm through --shadow-xl
  • Z-index: --z-base (1) through --z-toast (9999)

Tailwind v4 Integration

Tailwind v4 is installed via @tailwindcss/vite plugin. No preflight — existing CSS is preserved.

@theme inline Mapping

The @theme inline block in index.css maps your tokens to Tailwind utilities:

shadcn/ui standard names (used by shadcn components internally):

  • bg-primary → var(--accent) (your accent color)
  • text-primary-foreground → #ffffff (text on primary buttons)
  • bg-background → var(--bg-app)
  • text-foreground → var(--text-primary)
  • bg-card → var(--bg-surface)
  • bg-secondary / bg-muted / bg-accent → var(--bg-elevated) (shadcn "accent" = hover bg)
  • text-muted-foreground → var(--text-muted)
  • bg-destructive → var(--status-error)
  • border (default) → var(--border-default)
  • ring-ring → var(--accent)

Custom names (use in your own Tailwind code):

  • bg-app, bg-surface, bg-elevated, bg-inset — layout backgrounds
  • bg-accent-primary, bg-accent-hover — accent color (distinct from shadcn bg-accent)
  • bg-error, bg-warning, bg-success, bg-info — status colors
  • text-text-primary, text-text-secondary, text-text-muted — text colors
  • border-border-subtle — subtle borders

Specificity Rules

  • Unlayered CSS (existing App.css) always wins over Tailwind's @layer utilities
  • When migrating a component to Tailwind, remove its CSS rules to avoid conflicts
  • rgba() values stay as-is until converted to oklch() or Tailwind opacity modifiers (bg-primary/15)

shadcn/ui Components

Setup

  • Config: components.json (style: new-york, jsx, zinc base)
  • Utility: src/lib/utils.js — cn() function (clsx + tailwind-merge)
  • Path alias: @/* → ./src/* (in vite.config.js + jsconfig.json)
  • Components: src/components/ui/*.jsx

Available Components

ComponentImportUsage
Button@/components/ui/buttonActions, CTA. Variants: default, destructive, outline, secondary, ghost, link
Dialog@/components/ui/dialogModal dialogs. Use DialogContent, DialogHeader, DialogTitle, etc.
Input@/components/ui/inputForm text inputs
Badge@/components/ui/badgeStatus badges. Variants: default, secondary, destructive, outline
Table@/components/ui/tableData tables (Table, TableHeader, TableBody, TableRow, TableCell)
DropdownMenu@/components/ui/dropdown-menuContext menus, action menus
Tooltip@/components/ui/tooltipHover tooltips (needs TooltipProvider)
Sonner (Toast)@/components/ui/sonnerToast notifications via sonner
Card@/components/ui/cardContent cards (Card, CardHeader, CardTitle, CardContent)
Tabs@/components/ui/tabsTab navigation (Tabs, TabsList, TabsTrigger, TabsContent)

Usage Pattern

import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { cn } from '@/lib/utils'

function MyComponent({ isLate }) {
  return (
    <div className="flex items-center gap-2">
      <Badge variant={isLate ? 'destructive' : 'default'}>
        {isLate ? 'Late' : 'On Time'}
      </Badge>
      <Button variant="outline" size="sm">
        View Details
      </Button>
    </div>
  )
}

Adding New shadcn Components

npx shadcn@latest add <component-name> --yes --overwrite

Components generate as .jsx into src/components/ui/. No TypeScript cleanup needed.

Theme System

Theme Presets

Themes work by swapping CSS custom property values via [data-theme="X"] selectors in shared/theme.css. A ThemeToggle component persists the choice to localStorage.

Example presets to support:

GroupThemes
AutoSystem (follows OS)
StandardDark (default), Light, Dim, High Contrast
PopularMidnight, Nord, Dracula, Catppuccin
ClassicSolarized Dark, Solarized Light

Adding a New Theme

  1. Add theme object to THEMES array in ThemeToggle.jsx
  2. Add [data-theme="your-theme"] selector block in theme.css with all token overrides
  3. Light themes: set color-scheme: light and ensure stronger contrast (--text-primary: #0F172A)

Component Patterns

KPI Cards

Use a consistent KPICard component for metric cards across all projects:

  • Grid: 4 columns desktop, 2 mobile (@media max-width: 768px)
  • Structure: label, value (large), unit/trend, optional delta

Charts (Recharts)

Use shared tooltip styles if your project exports them:

import { CHART_TOOLTIP } from '@your-shared/constants'
<Tooltip {...CHART_TOOLTIP} />

Legend standard (enforced — e.g. via scripts/lint-chart-legends.mjs). A multi-series chart (2+ bars/lines/areas, or a .map() that emits a series per datum) MUST give the reader a labeled key for its colors, two ways:

  1. Visible legend — add <Legend wrapperStyle={{ fontSize: 11 }} /> (11px keeps it readable on compact cards). Every series needs a name= prop so the legend (and tooltip) labels it.
  2. Color-aware tooltip — the popup must show each series' color, not a uniform gray. A shared CHART_TOOLTIP that sets itemStyle:{color:'var(--text-secondary)'} flattens every row to one color. On a multi-series chart, pass a custom content= that colors each row by entry.color instead:
// Color-aware tooltip: a mini legend in the popup (swatch + name + value).
function ChartTooltip({ active, payload, label }, fmt = (v) => v) {
  if (!active || !payload?.length) return null
  return (
    <div className="chart-tooltip">
      <p className="tooltip-label">{label}</p>
      {payload.map((e, i) => (
        <p key={i} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ width: 9, height: 9, borderRadius: '50%', background: e.color }} />
          <span>{e.name}</span>
          <span style={{ marginLeft: 'auto', fontWeight: 600 }}>{e.value != null ? fmt(e.value) : '—'}</span>
        </p>
      ))}
    </div>
  )
}
// ...
<Tooltip content={<ChartTooltip />} />
  • Single-series charts are exempt — the card title names the one series; a legend is noise.
  • Custom / non-<Legend> key (e.g. a color-dot caption or a deliberately key-less chart): annotate it so lint skips it — {/* chart-legend-ok: <reason> */} on or just above the chart element.
  • If using a ratcheted lint gate: existing charts are grandfathered; it fails only on a NEW multi-series chart that lacks a legend or color-aware tooltip.

A lint that checks legend presence is satisfied by a bare <Legend />, so the check doesn't prove the legend is useful.

Reference legends (a key to a table, not a chart). When a legend explains what the badges, marks or colours in a table mean, build it from one shared panel component rather than per-view markup:

  • Make the swatch the real component the table renders (or its real CSS class), so the key restyles itself when the table does. Deliberately offer no items=[{color,label}] data prop: a hand-written hex is a copy that silently drifts from the CSS it describes.
  • Group with an eyebrow label and a hairline, not nested bordered cards, which read as separate panels inside a collapsible.
  • Drive layout with one auto-fit column width, not a per-view column count, and persist open/closed state in storage with a safe fallback when storage throws.

Additional chart patterns:

  • Dark tooltips require THREE props: contentStyle, labelStyle, itemStyle
  • Bundle split: Keep Recharts in manualChunks (vite.config.js)
  • ResponsiveContainer: height={isMobile ? 200 : 250} standard

Tables

  • table-layout: fixed + <colgroup> for resizable columns
  • Column widths persist to localStorage
  • Sticky headers: position: sticky; top: 0; z-index: var(--z-sticky)
  • Alternate row coloring: tr:nth-child(even) with subtle surface variation

Modals

Use a shared modal component with:

  • Focus trap (useModalA11y or native <dialog> + showModal())
  • Overlay-click-to-close: if (e.target === e.currentTarget) onClose() on the overlay — do NOT use stopPropagation on inner content (breaks children that render outside the DOM tree via ReactDOM.create* APIs)
  • Backdrop: rgba(0, 0, 0, 0.5) with optional blur
  • Escape key close
  • Scroll lock on body while open

Collapsible groups — Expand all / Collapse all

Any view that renders a list of collapsible sections/accordions/groups MUST offer bulk Expand all / Collapse all controls — don't re-implement per-group useState(open) with no bulk control, and don't let each new grouped view invent its own toggle shape.

  • Extract a shared hook (useExpandCollapseAll(defaultOpen)) that returns { isOpen(id), toggle(id), expandAll(), collapseAll() } and tolerates a changing id set — newly-loaded groups should inherit the current default so it works before data loads and after the list grows.
  • Extract a paired control component for the toolbar/filter bar (<ExpandCollapseControls onExpandAll={...} onCollapseAll={...} />).
  • Lift the per-group open state to the parent so the bulk controls actually drive every group, rather than each group owning its own isolated state.

Apply this to every new grouped/accordion view, not just the first one that needed it — it's cheap to reuse and expensive to reinvent per view.

CSS Architecture

LocationScope
shared/theme.cssDesign tokens (all projects)
shared/animations.cssKeyframes (all projects)
{project}/src/index.cssTailwind imports + @theme mapping
{project}/src/App.cssVanilla CSS (view-specific styles)
{project}/src/components/ui/shadcn/ui components

Migration Path

When rewriting a view to use Tailwind + shadcn:

  1. Build the new version alongside the old (or in a test route)
  2. Use shadcn components for structure (Dialog, Table, Card, Tabs, Button)
  3. Use Tailwind utilities for layout and spacing
  4. Remove the old CSS rules from App.css
  5. Keep domain-semantic styles (status colors, chart configs) in CSS or constants

Aligning an Existing Component to the Design System

Lighter than a full rewrite — use this when reskinning a component that already works but has drifted off-token (hardcoded hex colors, arbitrary px values, transition: all). Observed working loop, confirmed across multiple sessions:

  1. Read the source of truth first — the token file (this doc's Color Tokens section, or wherever your project defines them) plus the reference primitives the rest of the app already uses (card/badge/button/modal components). Match those; don't invent a parallel style.
  2. Grep the target for hardcoded drift before editing:
    grep -nE "#[0-9a-fA-F]{3,6}|rgba?\(" <target>.css   # raw colors that should be tokens
    grep -nE "[0-9]+px" <target>.css                     # arbitrary sizes vs your spacing/type scale
    grep -n "transition: all" <target>.css               # vague transitions — name the properties
  3. Rewrite CSS to tokens — swap raw values for token references, arbitrary px for the spacing/type scale, transition: all for explicit named properties. Keep domain-semantic colors (status/category colors that are intentionally not themed) as-is.
  4. Build and verify in a real browser — a token swap can silently change contrast or layout. Look at an actual screenshot, not bounding-box math or computed geometry.

Accessibility

  • Color contrast: 4.5:1 body, 3:1 large text (High Contrast theme = WCAG AAA)
  • Interactive targets: min 44x44px on mobile
  • Focus indicators: visible ring via focus-visible:ring-ring/50
  • Reduced motion: @media (prefers-reduced-motion: reduce) zeroes all animations
  • Status: never color-only — combine with icon/text

Anti-Patterns to Avoid (AI-Slop Signals)

These patterns appear in the majority of AI-generated UIs. Avoid them by default unless there is a specific, intentional reason.

Anti-patternWhat it looks likeBetter alternative
Purple/indigo gradientsbackground: linear-gradient(135deg, #6366F1, #8B5CF6) as a "hero"Use your --accent token consistently; gradients only for data viz
Inter-by-defaultLoading Inter because it's the AI default, not because it fitsUse fonts with discipline (defined scale, no arbitrary sizes)
Bento grids3x2 card grid where every card has a different height and an icon top-leftGrid layout with consistent card heights; icon choice driven by meaning, not decoration
Glass morphismbackdrop-filter: blur(12px) + background: rgba(255,255,255,0.1) + border: 1px solid rgba(255,255,255,0.2)Solid surface elevation using --bg-elevated; blur only for modals over complex backgrounds
Spark lines everywhereTiny 40px sparklines added to every metric card to look "data-rich"Sparklines only when the trend is the insight; use plain numbers when the current value is what matters
Side-tab indicator bordersLeft border on active tab item as the only active state signalCombine border with background color change; border-only fails on low contrast themes
Identical pill buttonsAll buttons are border-radius: 999px regardless of contextRadius should match the component family — table action buttons at --radius-sm, modal CTAs at --radius-md
Decorative hero illustrations3D robot or abstract illustration as page headerData visualization or functional illustration only; no illustration if it conveys no information
Shadow stackingbox-shadow on every card + every button + every inputPick one elevation tier per component type and stick to it
Random animationtransition: all 0.3s ease added to every elementOnly transition properties that actually change; explicit property names + durations matched to interaction type

Porting a Showcase Variant to Real Code

After /design-showcase and the user picks a variant, porting it into the real component has a specific failure mode worth calling out.

The drift trap: Worktree agents dispatched via Agent({ isolation: "worktree" }) branch from a shared reference point, NOT current HEAD. If parallel sessions have been modifying the target file, the worktree agent ports against a stale base — cherry-picking then conflicts.

Detection: before accepting an agent's port commit, check git log <worktree-base>..HEAD -- <target-file>. If non-empty, the agent was working from stale context.

Resolution pattern:

  1. If the CSS additions are purely additive (new scoped classes) — cherry-pick with git checkout --ours <target.jsx> to keep your current JSX and accept ONLY the CSS.
  2. Apply the JSX design edits directly with the Edit tool against current HEAD.
  3. Build, commit, push.

Prefer additive edits over reordering. If the showcase variant spec includes "reorder sections", save that for a separate follow-up — it's much riskier than adding a new pill or strip. Ship the high-signal additions first.

Tactical Patterns

  • Recharts tooltips: Need THREE style props for dark theme (contentStyle, labelStyle, itemStyle)
  • Recharts bundle: Split via manualChunks in vite.config.js — always keep this config
  • Dropdown in scrollable tables: position:fixed + getBoundingClientRect() — absolute gets clipped by overflow-x:auto
  • localStorage validation: Cross-reference saved columns against DEFAULT_COLUMNS to filter stale entries
  • getDueDateCategory "this week" filter: must include late + today + tomorrow + thisWeek (not just thisWeek)
  • ISO date timezone shift: new Date('2026-03-20') → midnight UTC → day before in local timezone. Fix: parse YYYY-MM-DD as new Date(year, month-1, day) for local display. Don't round-trip already-ISO strings through toISOString()
  • Unstable array/object deps + AbortController = infinite loop: When a useEffect depends on a prop-derived array (e.g. const ids = idsProp || []) and the parent passes a fresh literal each render, the effect re-fires every render. If that effect creates an AbortController with a cleanup that calls abort(), every render aborts the in-flight fetch. Symptom: modal stuck on "Loading..." forever, network panel shows canceled requests in a tight loop. react-hooks/exhaustive-deps does NOT catch it — the dep is listed correctly, just unstable. Fix: memoize the derived value against a primitive key (e.g. array.join('|')) or have the parent wrap in useMemo.
  • CSS shorthand vs longhand override: overflow: hidden (shorthand) is NOT reliably overridden by a later overflow-y: auto (longhand) in some cascade scenarios. Use the matching shorthand (overflow: hidden auto) to override. Also: overflow-y: auto is a no-op when the container has min-height instead of height; min-height is ignored on <td> elements per CSS spec.
  • Flex scroll chain requires every ancestor to be a flex container: For overflow-y: auto on a deeply nested element to work, every ancestor must be display: flex; flex-direction: column with min-height: 0 (or height: 0). One plain block container breaks flex: 1 on its children — content overflows without constraint.
  • CSS pattern audit before adding styles: Before adding @keyframes or new CSS classes to a large App.css, grep existing patterns first to avoid duplicate keyframes and conflicting class names.

See Also

  • /design-showcase — for exploratory redesigns, run a showcase first. Dispatches a single Opus agent that builds a self-contained HTML page with 3-7 design variants side-by-side. User picks a direction, then implement using the tokens and patterns above.
  • /design-reverse-engineer — extract design tokens and component patterns from any URL; use when anchoring a new view to a proven reference before building
  • /webapp-testing — Playwright visual verification after implementing a chosen variant
  • reference-sites.md (in .claude/skills/design-reverse-engineer/) — curated list of high-quality reference sites by domain

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.