frontier/skills/ui-design/SKILL.md
React UI design principles and conventions. Load when building or modifying any user interface or React components. Covers application type detection, visual standards, component design and structure, Mantine (business apps) and Tailwind (consumer apps), accessibility, responsiveness, state management, data fetching, testing, and in-app help patterns.
npx skillsauth add jon23d/skillz ui-designInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Business-facing (internal tools, dashboards, admin panels, B2B SaaS): Use Mantine. Do not introduce Tailwind.
Consumer-facing (marketing sites, consumer products): Use Tailwind CSS + Radix UI for accessible primitives. Do not introduce Mantine.
Do not mix the two systems.
One component, one responsibility. A component file should not exceed 150 lines.
Separate data from presentation. A component that calls useQuery/useMutation should not contain complex JSX — extract data-fetching into a custom hook, render focused children.
// Good — hook owns data, page composes focused children
function useUserDashboard() {
const user = useQuery({ queryKey: ['user'], queryFn: fetchUser })
const projects = useQuery({ queryKey: ['projects'], queryFn: fetchProjects })
return { user, projects }
}
function UserDashboard() {
const { user, projects } = useUserDashboard()
return (
<Stack>
<UserHeader user={user.data} isLoading={user.isLoading} />
<ProjectList projects={projects.data} isLoading={projects.isLoading} />
</Stack>
)
}
Extract every visually distinct section as its own component. More than 3 useState calls is a smell. JSX nesting deeper than 3 levels means you missed an extraction.
One component per file. Related files in a folder:
UserCard/
├── index.ts
├── UserCard.tsx
├── UserCard.test.tsx
└── types.ts
Explicit TypeScript interfaces. Required props are necessary, optional have defaults. Prefer callbacks over store references. Never use React.FC.
Prefer composing smaller components over boolean flag props (showHeader, compact, withBorder).
Extract conditionals and transformations into variables before the return statement.
Never use useEffect + useState for data fetching.
@/services. Services call the typed client, return typed objects.as SomeType.useState or useReduceruseState.Use Mantine components before building custom. Style with classNames + CSS Modules → Mantine CSS variables → styles prop. Never hardcoded hex values — use theme tokens. Forms with @mantine/form.
Tailwind utility classes exclusively. Radix UI for interactive primitives. Establish design tokens in tailwind.config.ts. No arbitrary values except one-off pixel-perfect needs. Prettier plugin for class ordering.
alt text. Decorative: alt="".role and aria-*.<button> for actions, <a> for navigation. Never <div onClick>.aria-pressed. Dialogs: role="dialog", aria-modal, focus trapped, Escape closes.Design mobile first. Mantine: responsive props. Tailwind: mobile-first breakpoint prefixes. No hardcoded widths for content containers.
Every data-dependent component handles three states: loading, error, success. Skeleton loaders, not spinners. Actionable error messages with retry. Empty states are specific, explain the entity, and offer a primary action.
One-sentence explanations for controls and icons. Trigger on hover and keyboard focus. 300–500ms delay. Never critical info only in tooltip (invisible on touch).
2–4 sentence inline explanations for non-obvious form fields and settings. Place after field label. Keyboard-accessible.
Always-visible description beneath inputs with non-obvious purpose. Distinct from validation errors. Concise.
Icon/illustration, specific heading, 1–2 sentences explaining the entity, primary action button to create first item. Never generic "Nothing here yet".
After completing UI changes, take screenshots from e2e tests (not separate scripts). Add page.screenshot() calls into tests, then remove before committing. Cover: default state, interaction states, validation errors, success, mobile viewport when relevant. Name as route_state-description_viewport.png.
Use React Testing Library. Query by accessible role, label, or visible text — never getByTestId. Use userEvent. Test loading, error, success states. Domain objects from test factories — no inline literals.
useEffect + useState for data fetching → use React Queryresponse.json() as SomeType → validate with Zoddiv/span with onClick → use <button>development
Use when adding or modifying environment variable handling in TypeScript projects or monorepos — especially when using process.env directly, missing startup validation, sharing env schemas across packages, or encountering "undefined is not a string" errors at runtime from missing env vars.
testing
Use when creating a new skill, editing an existing skill, writing a SKILL.md, or verifying a skill works before deployment.
development
React UI design principles and conventions. Load when building or modifying any user interface or React components. Covers application type detection, visual standards, component design and structure, Mantine (business apps) and Tailwind (consumer apps), accessibility, responsiveness, state management, data fetching, testing, and in-app help patterns.
development
Use when setting up ESLint and/or Prettier in a TypeScript project, adding linting to an existing TypeScript codebase, or configuring typescript-eslint, eslint-config-prettier, or related packages.