Build and develop web frontend applications using the Open Elements tech stack (Next.js, React, TypeScript, Tailwind CSS, shadcn/ui, pnpm). Use when the user asks to create a new frontend project, add pages or components, set up routing, or develop features in a web application. Not for simple HTML snippets — use for real application development.
Build production-grade web frontend applications using the Open Elements tech stack. Follow these steps to create or extend a frontend project.
Stack: Next.js + React 18 + TypeScript (strict) + Tailwind CSS + shadcn/ui + pnpm
IMPORTANT: All conventions from ../../conventions/typescript.md apply. When in doubt, defer to that document.
For design direction and visual quality, apply the frontend-design skill. Key principles:
open-elements-brand-guidelines skill.pnpm create next-app <project-name> --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
cd <project-name>
Edit next.config.ts to set standalone output (required for Docker deployments):
const nextConfig = {
output: 'standalone',
};
IMPORTANT: Next.js evaluates next.config.ts — including rewrites() — at build time. Any environment variable used in next.config.ts (e.g., BACKEND_URL for API rewrites) must be available during pnpm build. In Docker, this means declaring it as a build argument (ARG) and setting it as an environment variable (ENV) in the Dockerfile before the build step. A runtime-only environment: entry in docker-compose.yml is too late — the rewrite rules are already baked into the build output. See fullstack-architecture.md for the Dockerfile and Docker Compose configuration.
Verify tsconfig.json has "strict": true. This is non-negotiable.
pnpm dlx shadcn@latest init
When prompted, select the defaults that match the project's design direction. Then install the components needed:
pnpm dlx shadcn@latest add button card input table dialog
Add more components as needed during development.
Add to the project's .mcp.json:
{
"mcpServers": {
"shadcn": {
"command": "npx",
"args": ["shadcn@latest", "mcp"]
}
}
}
Configure Open Elements brand colors in tailwind.config.ts so they are available as utility classes. Use the open-elements-brand-guidelines skill to get the exact color values.
IMPORTANT: shadcn/ui components reference semantic CSS custom properties (e.g., --color-background, --color-popover, --color-border). These must be defined in the @theme block of src/app/globals.css, mapped to the project's brand colors. Without them, dialogs, dropdowns, tables, and inputs will render with transparent backgrounds and missing borders.
Required semantic tokens (each with -foreground counterpart where applicable):
| Token | Purpose |
|---|---|
background / foreground | Main page background and text |
card / card-foreground | Card surfaces |
popover / popover-foreground | Dialogs, dropdowns, popovers |
muted / muted-foreground | Hover states, disabled elements, secondary text |
accent / accent-foreground | Highlighted items (e.g., hovered select option) |
primary / primary-foreground | Primary action buttons |
secondary / secondary-foreground | Secondary action buttons |
destructive / destructive-foreground | Delete/error buttons |
border | All component borders (tables, cards, inputs) |
input | Input field borders |
ring | Focus ring indicators |
See the shadcn/ui Theming docs for details. Never hardcode colors in component files — always use semantic tokens.
pnpm dev
Confirm the dev server starts without errors.
Create pages in src/app/ following Next.js App Router conventions:
src/app/page.tsx — home pagesrc/app/<route>/page.tsx — additional pagessrc/app/layout.tsx — root layout with consistent header/navigation and footerIMPORTANT: Pages that fetch data from a backend API must not be statically pre-rendered. Use export const dynamic = 'force-dynamic' to ensure request-time rendering.
Place reusable components in src/components/. Follow these conventions:
p-4, p-6, gap-4, gap-6)max-w-screen-xl mx-auto)sm:, md:, lg:)All user-facing text must be i18n-ready from the start:
page.section.element)"Welcome, {name}" not "Welcome, " + name)Intl.DateTimeFormat, Intl.NumberFormat)Use the testing framework present in the project (Jest, Vitest, or Node test runner):
pnpm test
it('should return empty array when no items exist')describe blockspnpm installpnpm devpnpm buildpnpm testpnpm lintpnpm tsc --noEmitBefore considering a feature complete, verify:
pnpm build succeeds without errorspnpm lint passespnpm tsc --noEmit reports no type errorsconsole.log in production code — use console.error, console.warn, console.infonext.config.tsglobals.css @theme block