--- name: frontend-code-style description: Use when writing or refactoring React + Tailwind v4 code in this repo. Covers component sizing, CSS deduplication via primitives + shared constants, design tokens via @theme, the "adjust state during render" pattern over setState-in-effect, directory layout (ui/message/sidebar/connections), and what NOT to extract. --- # Frontend Code Style ## Overview The frontend is React 19 + Tailwind v4 + Radix + Vite. Visual styling flows from `@theme` tokens in [index.css](frontend/src/index.css); structure flows from a feature-folder layout under `components/`. This skill codifies the conventions so new code lands consistent without inventing parallel systems. **Core principle:** Extract when the same shape appears 2+ times OR when one file mixes 3+ unrelated concerns. Otherwise keep classNames inline next to the JSX they style. ## Directory Layout ``` src/ ├── api/ backend API clients ├── hooks/ cross-cutting React hooks (incl. ConnectionProviders) ├── lib/ pure helpers (formatters, styles constants, openInNewTab) ├── pages/ top-level routes — MessageView, Sandbox └── components/ ├── ui/ cross-cutting primitives (Button, IconButton, │ Panel, Strip, EmptyCard, CategoryBadge, Toggle, icons) ├── message/ right-pane (MessageHeader, MessagePreview, │ MessageTabs, HtmlSource, TechInfo, CodePane, │ HtmlCheck/) ├── sidebar/ left-pane (Sidebar, SidebarToolbar, MessageList, │ DeleteAllPrompt, ConnectionErrorBanner) ├── connections/ cloud/relay/webhook dialogs + dialogAtoms, │ dialogStyles, lockedFields, SettingsMenu └── CodeSamples/ Sandbox empty-state code samples ``` **Rules:** - A component used by only one feature belongs in that feature folder, not in `ui/`. - `ui/` is for primitives reused by 2+ features. - A component that hits 400+ lines or has clear inner sub-components becomes a folder with `index.tsx` (see `HtmlCheck/`). - Tests sit next to the file they test (`Sidebar.test.tsx` next to `Sidebar.tsx`). ## CSS Conventions ### Tokens come first Colors, fonts, accent levels live in `@theme` in `index.css`. Use them as Tailwind utilities (`bg-surface-base`, `text-fg-muted`, `text-warning`, `font-mono`). Never hard-code hex in components — add a `--color-` token instead. ### Tailwind first, constants when reused Single-use className: write inline. Multi-line className constant: extract a local `const xCss = [...].join(' ')` next to where it's used. Use this when a className gets wide enough to wrap, or has stateful `data-[…]:` variants worth naming. Cross-component reuse: extract a primitive component (preferred) or a shared constant in `lib/styles.ts` (when callers need to extend with their own utilities). ```tsx // ❌ Don't: invent a one-off CSS constant for a single use const wrapperCss = 'm-0' return
…
// ✅ Do: inline when used once return
…
// ✅ Do: name when wide + stateful const row = [ 'group grid grid-cols-[1fr_auto] gap-x-3 px-4 py-3', 'data-[read=true]:bg-surface-base', 'data-[active=true]:!bg-accent data-[active=true]:hover:!bg-accent', ].join(' ') ``` ### Variants via `data-*` attributes Project convention is `data-[variant=primary]:bg-accent` style, not `clsx`/`cva`. See [Button.tsx](frontend/src/components/ui/Button.tsx), [IconButton.tsx](frontend/src/components/ui/IconButton.tsx), `dialogStyles.btn`. Match this pattern when adding new variant-driven components. ### Shared structural constants (`lib/styles.ts`) Use a constant (not a component) when the same CSS fragment appears across different elements (``, `