Mandatory frontend technology requirements for PolicyEngine dashboards and interactive tools — Tailwind CSS v4, Next.js (App Router), @policyengine/ui-kit theme, Vercel deployment
From essentialnpx claudepluginhub policyengine/policyengine-claude --plugin data-scienceThis skill uses the workspace's default tool permissions.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Optimizes cloud costs on AWS, Azure, GCP via rightsizing, tagging strategies, reserved instances, spot usage, and spending analysis. Use for expense reduction and governance.
Authoritative specification for all PolicyEngine frontend projects (dashboards and interactive tools). Any agent building or validating a frontend MUST load this skill and follow every requirement below. Where another agent's instructions conflict with this spec, this spec wins.
The application MUST use Tailwind CSS v4 for all styling. Tailwind utility classes are the primary styling mechanism.
tailwindcss (v4+)globals.css containing:
@import "tailwindcss";
@import "@policyengine/ui-kit/theme.css";
tailwind.config.ts or tailwind.config.js — Tailwind v4 uses @theme in CSS insteadpostcss.config.js or postcss.config.mjs — Tailwind v4 does not require PostCSS@tailwind base; @tailwind components; @tailwind utilities; — use @import "tailwindcss" instead*.module.css) for layout or stylingglobals.css (which imports ui-kit theme)The application MUST install @policyengine/ui-kit and use it as the primary component library and design token source. MUST use ui-kit components when an equivalent exists — do NOT rebuild components that ui-kit already provides.
bun add @policyengine/ui-kitglobals.css: @import "@policyengine/ui-kit/theme.css";Component availability table:
| Dashboard need | ui-kit component |
|---|---|
| Page shell | DashboardShell |
| Header with logo + nav | Header (light/dark variants, navLinks prop) |
| Two-column layout | SidebarLayout + InputPanel + ResultsPanel |
| Single-column narrative | SingleColumnLayout |
| Buttons | Button (4 variants, 3 sizes) |
| Cards | Card, CardHeader, CardTitle, CardContent, CardFooter |
| Badges | Badge (6 variants) |
| Tab navigation | Tabs, TabsList, TabsTrigger, TabsContent |
| Currency input | CurrencyInput |
| Number input | NumberInput |
| Select dropdown | SelectInput |
| Checkbox | CheckboxInput |
| Slider | SliderInput |
| Input grouping | InputGroup |
| KPI display | MetricCard (currency/percent, trends) |
| Summary text | SummaryText |
| Data tables | DataTable |
| Charts | ChartContainer, PEBarChart, PELineChart, PEAreaChart, PEWaterfallChart |
| Branding | PolicyEngineWatermark, logos.* |
| Utilities | formatCurrency, formatPercent, formatNumber |
Component precedence rule: When building UI:
@policyengine/ui-kit if it has the componentThe application MUST load design tokens from @policyengine/ui-kit/theme.css. This single CSS import provides all colors, spacing, typography, and chart tokens.
globals.css: @import "@policyengine/ui-kit/theme.css";<link> — the theme is bundled with ui-kitvar(--font-sans)Token usage patterns:
| Context | Approach | Example |
|---|---|---|
| React components | Tailwind semantic classes | className="bg-primary text-foreground" |
| Brand palette | Tailwind direct classes | className="bg-teal-500 text-gray-600" |
| Recharts (SVG) | CSS vars directly in fill/stroke | fill="var(--chart-1)" |
| Inline styles | CSS vars | style={{ color: "var(--primary)" }} |
The application MUST use Next.js with the App Router.
create-next-app or equivalent to scaffold with App Routernext.config.ts at the project rootapp/ directory with layout.tsx and page.tsx.ts/.tsx files, tsconfig.json)pages/ directory)The application MUST use bun as the package manager.
bun install instead of npm installbun run dev, bun run build instead of npm run dev, npm run buildbunx vitest run instead of npx vitest runbun.lock lockfile (not package-lock.json)The application MUST be deployed using Vercel.
vercel.json at the project root with appropriate configurationoutput: 'export' in next.config.ts for static export, unless the dashboard requires server-side renderingNEXT_PUBLIC_* prefixpolicy-engine Vercel scopeWhen building custom components not available in @policyengine/ui-kit, the application SHOULD use shadcn/ui primitives as the base layer.
bunx shadcn@latest initbackground, foreground, primary, muted)@policyengine/ui-kit component exists@import "tailwindcss";
@import "@policyengine/ui-kit/theme.css";
body {
font-family: var(--font-sans);
color: var(--foreground);
background: var(--background);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
The single @import "@policyengine/ui-kit/theme.css" provides:
:root variables — shadcn/ui semantic tokens (--primary, --background, --chart-1, etc.)@theme inline — Bridges :root vars to Tailwind utilities (bg-primary, text-foreground)@theme — Brand palette (bg-teal-500, text-gray-600), font sizes, spacing, breakpointsimport './globals.css'
import { Inter } from 'next/font/google'
import type { Metadata } from 'next'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'DASHBOARD_TITLE - PolicyEngine',
description: 'DASHBOARD_DESCRIPTION',
icons: { icon: '/favicon.svg' },
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
)
}
// Prefer ui-kit components:
import { MetricCard, Button, Card, CardContent } from '@policyengine/ui-kit';
import { formatCurrency } from '@policyengine/ui-kit';
// Use Tailwind classes with semantic and brand tokens for custom layouts:
<div className="bg-background border border-border rounded-lg p-4 flex flex-col gap-1">
<span className="text-sm text-muted-foreground font-medium">Metric title</span>
<span className="text-2xl font-bold text-foreground">{formatCurrency(1234)}</span>
</div>
// Brand colors when needed:
<div className="bg-teal-500 text-white hover:bg-teal-600">Primary teal</div>
// Responsive design uses Tailwind breakpoint prefixes:
<main className="max-w-content mx-auto px-6 py-4 font-sans text-foreground">
<div className="flex gap-6 md:flex-col">
{/* sidebar collapses at md breakpoint */}
</div>
</main>
// Recharts uses CSS vars directly:
<Line stroke="var(--chart-1)" />
<Bar fill="var(--chart-2)" />
<CartesianGrid stroke="var(--border)" />
// Header with logo and structured nav links:
import { Header, logos } from '@policyengine/ui-kit';
<Header
variant="dark"
logo={<img src={logos.whiteWordmark} alt="PolicyEngine" className="h-5" />}
navLinks={[
{ slug: 'research', text: 'Research', href: 'https://policyengine.org/research' },
]}
>
<span className="ml-2">Dashboard Title</span>
</Header>
DASHBOARD_NAME/
├── app/
│ ├── layout.tsx # Root layout — Inter font + globals.css
│ ├── page.tsx # Main dashboard page
│ ├── globals.css # @import "tailwindcss" + @import ui-kit theme
│ └── providers.tsx # React Query provider (client component)
├── components/
│ └── (from plan.yaml) # Custom dashboard components (only if not in ui-kit)
├── lib/
│ ├── api/
│ │ ├── client.ts # API client (stubs or real)
│ │ ├── types.ts # Request/response TypeScript types
│ │ └── fixtures.ts # Mock data for stubs
│ ├── embedding.ts # Country detection, hash sync, share URLs
│ └── hooks/
│ └── useCalculation.ts # React Query hooks
├── public/
├── next.config.ts
├── tsconfig.json
├── package.json
├── vitest.config.ts
├── plan.yaml
├── CLAUDE.md
├── README.md
├── vercel.json
└── .gitignore
Production:
nextreact, react-domtailwindcss (v4+)@policyengine/ui-kit@tanstack/react-queryrecharts (if custom charts beyond ui-kit)react-plotly.js, plotly.js-dist-min (if maps)axiosDevelopment:
typescript, @types/react, @types/react-dom, @types/nodevitest, @vitejs/plugin-react, jsdom@testing-library/react, @testing-library/jest-domVitest is the test runner. Configure vitest.config.ts:
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./vitest.setup.ts'],
},
})
Note: @vitejs/plugin-react is only used by Vitest for JSX transform during testing — Vite is NOT used as the application bundler.
tailwind.config.ts or postcss.config.js — Tailwind v4 uses @theme in CSS@tailwind base; @tailwind components; @tailwind utilities; — use @import "tailwindcss"var(--font-sans) via Tailwind@policyengine/ui-kitgetCssVar() — it no longer exists. SVG accepts var() directly.