frontend-ui
npx skills add https://github.com/scientiacapital/skills --skill frontend-ui
Agent 安装分布
Skill 文档
Production SaaS: dashboards, pricing pages, data tables, onboarding, role-based UI â with WCAG 2.1 AA accessibility and Core Web Vitals performance baked in.
<quick_start>
Setup: Tailwind v4 + shadcn/ui
npx create-next-app@latest my-app --typescript --tailwind --eslint --app --src-dir
cd my-app && npx shadcn@latest init
npx shadcn@latest add button card dialog table form
Tailwind v4 â CSS-First (No tailwind.config.js)
/* app/globals.css */
@import "tailwindcss";
@theme inline {
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0 0);
--color-primary: oklch(0.205 0.042 264.695);
--color-primary-foreground: oklch(0.985 0 0);
--radius-lg: 0.5rem;
--radius-md: calc(var(--radius-lg) - 2px);
--radius-sm: calc(var(--radius-lg) - 4px);
}
Component Anatomy (shadcn/ui 2026)
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
{
variants: {
variant: { default: "bg-primary text-primary-foreground", outline: "border border-input" },
size: { default: "h-10 px-4 py-2", sm: "h-9 px-3", lg: "h-11 px-8" },
},
defaultVariants: { variant: "default", size: "default" },
}
)
// React 19: ref is a regular prop â no forwardRef
// data-slot: styling hook for parent overrides
function Button({ className, variant, size, ref, ...props }:
React.ComponentProps<"button"> & VariantProps<typeof buttonVariants>) {
return <button ref={ref} data-slot="button"
className={cn(buttonVariants({ variant, size, className }))} {...props} />
}
cn() Utility
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) }
Vite SPA Alternative
npm create vite@latest my-app -- --template react-ts
cd my-app && npm i -D @tailwindcss/vite && npx shadcn@latest init
Key differences from Next.js:
@tailwindcss/viteplugin (not postcss) â faster HMR, native Vite integrationVITE_env prefix (notNEXT_PUBLIC_), accessed viaimport.meta.env- Client-only â no Server Components, use React Query for data fetching
React.lazy()+<Suspense>replacesdynamic()for code splitting- Routing via React Router v7 or TanStack Router (not file-based)
Tailwind v4, shadcn/ui, component patterns, accessibility, forms, and performance guidance all apply equally to Vite SPAs. Only routing and data fetching genuinely differ.
See reference/vite-react-setup.md and reference/spa-routing.md.
</quick_start>
<success_criteria> Enterprise SaaS frontend is production-ready when:
- Accessible: WCAG 2.1 AA â keyboard nav, screen reader, focus management, 4.5:1 contrast
- Performant: LCP < 2.5s, INP < 200ms, CLS < 0.1 on 4G mobile
- Responsive: Mobile-first, works 320px-2560px, container queries for components
- Secure: No XSS vectors, CSP headers, sanitized user content
- Themed: Dark mode via CSS, design tokens in @theme, consistent spacing/color
- Composable: Server Components default, client boundary pushed to leaves
- Typed: TypeScript strict, Zod validation on all forms, no
any</success_criteria>
<core_principles>
- Server-First â Default to Server Components. Add
"use client"only for interactivity. Push client boundaries to leaf components. - Accessible-by-Default â Semantic HTML first (
<nav>,<main>,<article>). ARIA only when native semantics insufficient. - Composition Over Configuration â Small composable components. Compound pattern for complex UI. Context at boundaries.
- Progressive Disclosure â Essential info first. Reveal complexity on demand. Reduce cognitive load.
- Mobile-First â Design for smallest screen, enhance upward. Container queries for components. Touch targets >= 44px.
- Design Tokens â All visual values in CSS
@theme. Never hardcode. OKLCH for perceptual uniformity. - Type Safety E2E â Zod schemas shared client/server.
React.ComponentProps<>over manual interfaces. </core_principles>
<tailwind_v4>
Tailwind CSS v4 â Key Changes from v3
- No
tailwind.config.jsâ All config via CSS@themedirective @import "tailwindcss"â Replaces@tailwind base/components/utilities- OKLCH colors â Perceptually uniform, replaces hex/HSL
- Container queries built-in â
@container,@md:,@lg:prefixes @sourceâ CSS-native file scanning (replacescontentarray)- 70% smaller CSS â Automatic unused style elimination
@theme inlineâ shadcn/ui bridge: tokens without generated utilities
@theme {
--color-brand-500: oklch(0.55 0.15 250);
--font-sans: "Inter", system-ui, sans-serif;
--breakpoint-xs: 475px;
--animate-slide-in: slide-in 0.2s ease-out;
}
// Container queries â component-level responsive
<div className="@container">
<div className="grid grid-cols-1 @md:grid-cols-2 @lg:grid-cols-3 gap-4">
{items.map(item => <Card key={item.id} {...item} />)}
</div>
</div>
Migration: npx @tailwindcss/upgrade â See reference/tailwind-v4-setup.md.
</tailwind_v4>
<shadcn_ui>
shadcn/ui 2026
@theme inlineâ Bridges tokens with Tailwind v4data-slotâ Attribute-based styling hooks (replaces className overrides)- No
forwardRefâ React 19 ref as prop tw-animate-cssâ Replacestailwindcss-animatefor v4 compat- Radix or Base UI â Choose primitive library
// data-slot: parent can target child styles
function Card({ className, ref, ...props }: React.ComponentProps<"div">) {
return <div ref={ref} data-slot="card" className={cn("rounded-xl border bg-card", className)} {...props} />
}
// Style from parent:
<div className="[&_[data-slot=card]]:shadow-lg">
<Card>...</Card>
</div>
Dark mode: CSS custom property swap with .dark class. See reference/shadcn-setup.md.
</shadcn_ui>
<component_architecture>
Server vs Client Components
| Server Component (default) | Client Component ("use client") |
|---|---|
| Async data fetching, DB access | useState, useEffect, event handlers |
| Zero JS bundle, access to secrets | Browser APIs, third-party client libs |
Rule: Push "use client" to smallest leaf possible.
// Server page with client island
export default async function DashboardPage() {
const metrics = await getMetrics()
return (
<main>
<KPICards data={metrics} /> {/* Server-rendered */}
<RevenueChart data={metrics} /> {/* Client island */}
</main>
)
}
Key Patterns
- Compound components â
<Table>/<TableRow>/<TableCell>namespace composition - cva variants â Type-safe style variants with
class-variance-authority - React.ComponentProps â Replace manual interfaces, ref as regular prop
- data-slot â External styling hooks for parent-child overrides
- Polymorphic (asChild) â
Slotpattern for rendering as different elements - SPA code splitting â
React.lazy()+<Suspense>replaces Next.jsdynamic()
See reference/component-patterns.md for complete examples.
</component_architecture>
<saas_patterns>
Enterprise SaaS Patterns
Dashboard: Sidebar + Header + Main
<div className="flex h-screen">
<Sidebar className="w-64 hidden lg:flex" />
<div className="flex-1 flex flex-col">
<Header /> {/* Search, user menu, notifications */}
<main className="flex-1 overflow-auto p-6">
<KPIGrid metrics={metrics} />
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mt-6">
<RevenueChart data={revenue} />
<ActivityFeed items={activities} />
</div>
</main>
</div>
</div>
Pricing (3-Tier Conversion)
Anchor (low) | Conversion target (highlighted, “Most Popular”) | Enterprise (custom)
Monthly/annual toggle, feature comparison table, social proof. See templates/pricing-page.tsx.
Data Tables â shadcn Table + TanStack Table for sort/filter/paginate
State Trio â Every data component needs: Loading (Skeleton) | Error (retry action) | Empty (guidance)
Role-Based UI â hasPermission(user, "scope") guard for conditional rendering
See reference/saas-dashboard.md and reference/saas-pricing-checkout.md.
</saas_patterns>
Semantic HTML first â <header>, <nav>, <main>, <article>, <section>, <footer>
| Pattern | Implementation |
|---|---|
| Keyboard nav | Tab/Shift+Tab, Arrow keys in menus/tabs, Escape to close |
| Focus management | Trap in dialogs, restore on close, skip link |
| ARIA live regions | aria-live="polite" for dynamic content |
| Form errors | aria-invalid, aria-describedby, role="alert" |
| Loading states | aria-busy={true} on loading buttons |
| Contrast | 4.5:1 text, 3:1 UI components (OKLCH lightness channel) |
// Skip link
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:z-50">
Skip to main content
</a>
See reference/accessibility-checklist.md for per-component ARIA patterns.
<state_management>
State Decision Tree
| State Type | Solution | Example |
|---|---|---|
| URL state | nuqs / useSearchParams |
Filters, pagination, tabs |
| Server data | React Query / SWR | API data, user profile |
| Local UI | useState |
Form inputs, toggles |
| Shared parent-child | Lift state / Context | Accordion groups |
| Complex cross-cutting | Zustand | Cart, wizard, notifications |
Prefer URL state â shareable, bookmarkable, survives refresh. </state_management>
<data_fetching>
Data Fetching
| Pattern | When | How |
|---|---|---|
| Server Components | Default | async function Page() { const data = await db.query() } |
| Suspense streaming | Slow data | <Suspense fallback={<Skeleton/>}><SlowComponent/></Suspense> |
| Server Actions | Mutations | "use server" + revalidatePath() |
| React Query | Client real-time | useQuery({ queryKey, queryFn, refetchInterval }) |
| React Query (SPA) | Client-only apps | useQuery({ queryKey, queryFn }) with loaders â replaces Server Components |
| </data_fetching> |
- Shared Zod schema â Single source of truth for client validation and server action
- React Hook Form â
useFormwithzodResolver,mode: "onBlur" - shadcn Form â
<Form>/<FormField>/<FormItem>/<FormLabel>/<FormMessage> - Server Action â
safeParseon server, return field errors,revalidatePath
const schema = z.object({
name: z.string().min(2),
email: z.string().email(),
})
See reference/form-patterns.md and templates/form-with-server-action.tsx.
| Metric | Target | Quick Win |
|---|---|---|
| LCP < 2.5s | Main content visible | next/image with priority, next/font |
| INP < 200ms | Responsive interactions | Code-split heavy components with dynamic() |
| CLS < 0.1 | No layout shift | Reserve space for images/fonts, Skeleton loaders |
Tailwind v4 produces 70% smaller CSS automatically. See reference/performance-optimization.md.
Accessibility
- Keyboard navigation for all interactive elements
- Screen reader announces content meaningfully
- Focus indicators visible, skip link present
- Color contrast >= 4.5:1 (text), >= 3:1 (UI)
Performance
- LCP < 2.5s, INP < 200ms, CLS < 0.1
- Images via next/image, fonts via next/font
- Heavy components code-split with dynamic()
Responsive
- Works 320px-2560px, touch targets >= 44px
- Container queries for reusable components
Security
- No raw HTML injection without sanitization
- CSP headers, Zod validation client AND server
UX
- Loading / error / empty states for all data views
- Toast for mutations, confirm for destructive actions