Agentman App Style Guide
A portable design system for building (or restyling) an Electron / Next.js / React app with Tailwind in the Agentman house style. It is reverse-engineered from the production MedMan desktop app — these are the tokens, scales, components, and patterns the real app uses (or should use), not an aspirational spec.
It has two halves:
- Visual system — color, type, spacing, radius, elevation, motion (this file +
references/styleguide.md). - Component system — the primitive contracts, data-table pattern, app shell, feedback/overlays, and enforcement (
references/component-system.md).
Core identity: a warm terracotta brand ("Burnt Peach", #CB785D) over warm
neutrals (linen / gunmetal), deliberately not cold slate-gray. Dense, information-first
layout — this is a tool, not a marketing landing page. Restraint over decoration. Built on
shadcn-style primitives (Radix + class-variance-authority).
When to use this skill
Use it for any visual OR component decision in an Agentman-style app: setting up Tailwind tokens, building/designing a component (button, card, badge, input, select, dialog, table, toast, layout shell, tabs, empty state, skeleton), choosing a color for a status or category, picking spacing/radius/elevation/type, restyling a page, or running a design/component review. Don't wait for the user to say "styleguide" — if they're deciding how UI looks or how a component behaves, this is the source of truth.
How to apply it (the fast path)
- Tokens first. Drop
assets/tailwind.colors.jsintotailwind.config.jsundertheme.extend.colors→primary-*(Burnt Peach) + warm neutrals (gunmetal,grey,linen,cream,canvas,ghost). - Color decisions live in two files, never inline. Copy
assets/status-tone.ts(status → semantic color) andassets/category-colors.ts(taxonomy → color). Every badge/pill imports from these. A rawtext-purple-700in a component is a smell. - One primitive per UI role, and everyone goes through it. A button is
<Button>, not a raw styled<button>; a status pill is<StatusBadge>, not bespoke spans. The primitive is the only place a styling decision for that role is made. Seereferences/component-system.md. - Defaults. Body text
text-sm, resting weightfont-medium, radiusrounded-lg, elevationshadow-sm, app bgbg-canvas, content onbg-whitecards withborder-linen. - Read the references for full detail before a non-trivial build or review:
references/styleguide.md(visual) andreferences/component-system.md(components).
The visual system at a glance
Color
- Brand:
primary-500(#CB785D) for actions/links/focus;primary-600hover. - Text:
text-gunmetal(#383938, warm near-black) — never coldslate-900. - Surfaces:
bg-canvas(recessed ground) → white content card →bg-linen/bg-creamsub-surfaces. Figure/ground, Linear-style. - Semantic (fixed meaning): emerald = success, red = error, amber = warning, blue = info.
Defined once in
status-tone.ts. - Category colors (purple/violet/indigo/pink/sky): ONLY through
category-colors.ts.
Type (dense desktop scale)
text-sm(14px) = default body/cells/labels.text-xs(12px) = metadata/badges.text-lg/text-xl/text-2xlfor card/section/page titles.- Resting weight
font-medium;font-semiboldheadings;font-boldpage titles only. - Font: native system stack (no web font).
Spacing / radius / elevation
- 4px base. Card padding
p-4; form fieldsspace-y-4; sectionsspace-y-6. - Radius:
rounded-md(inputs),rounded-lg(cards/buttons, default),rounded-xl(large cards, sparingly),rounded-full(badges). Neverrounded-2xl+. - Elevation:
shadow-smresting,shadow-lgpopovers/dropdowns,shadow-xlmodals.
Motion
transition-colors duration-150on hovers. Entrances ≤ 350ms, ease-out, once per mount.- No hover scale/rotate, no infinite pulse on content, no animated gradients or blur blobs.
The component system at a glance
Full contracts in references/component-system.md. The essentials:
- One primitive per role. Build on Radix (headless behavior/a11y) + cva (variants)
cn()(clsx + tailwind-merge) + RadixSlot/asChild. One icon library (lucide-react). One toast helper (showToast).
- Core primitives:
Button(primary/secondary/ghost/destructive/link × sm/md/icon),StatusBadge(driven bystatus-tone.ts),Card,Input/Textarea+Label,Select/Combobox,Dialog,Tooltip,Checkbox. - Data table: one generic
AgentDataTable<TData>renderer + a per-featureuse<Feature>Table()hook (TanStack v8). StablegetRowId, stable cell renderers, persisted sort/filter/expansion, patch-single-row updates. Retire hand-rolled tables. - App shell: compose every page as
AppLayout→Sidebar/SideDock→PageHeader→PageTabs→ white content cards onbg-canvas. Pages don't reinvent chrome. - Feedback/overlays: one
Toast, oneSpinner, oneSkeleton, oneEmptyState, anErrorBoundaryper page. Flatbg-black/40scrims (no blur).
Canonical recipes
// Primary button (or use <Button variant="primary">)
<button className="bg-primary-500 hover:bg-primary-600 text-white font-medium px-4 py-2 rounded-lg transition-colors duration-150">Verify</button>
// Card
<div className="bg-white border border-linen rounded-lg p-4 shadow-sm">
<h3 className="text-lg font-semibold text-gunmetal mb-2">Coverage</h3>
<p className="text-sm text-grey">…</p>
</div>
// Status badge (driven by status-tone.ts)
import { statusTone, toneFor } from '@/utils/status-tone';
<span className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full border text-xs font-medium ${statusTone(toneFor(status))}`}>{label}</span>
// Input
<input className="w-full h-9 px-3 rounded-md border border-linen text-sm text-gunmetal placeholder:text-grey focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500" />
Anti-patterns (don't reproduce the drift)
Actual problems found auditing the live app — avoid them in any new app:
- Bypassed primitives. Don't style raw
<button>for actions (the app has 178 raw buttons vs 13 using the primitive). Go through<Button>. - Duplicate components. One
StatusBadge, not five; one table system, not 10+. - Multiple neutral ramps. No
slate-/gray-/neutral-/zinc-. One warm neutral system. - Two ramps for one meaning. Pick
emeraldfor success (notgreenandemerald). - Inline category colors. Route purple/indigo/etc. through
category-colors.ts. - Glassmorphism / gradients / over-rounding. No
backdrop-blur,bg-gradient-to-*,rounded-2xl+. - Cold slate text. Body is warm
gunmetal, neverslate-900/charcoal. - Mixed icon libraries / decorative motion. One icon set; no scale/rotate hovers or infinite pulses.
Enforcement
Primitives alone don't hold the line (the app proves it). Add a CI grep/lint gate banning,
in feature/page source: raw <button (outside ui/), slate-/gray-/neutral-/zinc-,
bg-gradient-to, backdrop-blur, rounded-2xl/rounded-3xl, inline
purple-/indigo-/violet-/pink-/sky- outside the category map, a second *StatusBadge*, a
second icon library, or a hand-rolled <table> outside the shared table system. Full
checklist in references/component-system.md ("Enforcement") and references/styleguide.md §9.
Bundled resources
references/styleguide.md— the complete VISUAL system (color, type, spacing, layout, motion).references/component-system.md— the complete COMPONENT system (primitive contracts, data table, app shell, feedback/overlays, enforcement).assets/tailwind.colors.js— color block to paste intotailwind.config.js.assets/status-tone.ts— semantic status → color, single source of truth for status badges.assets/category-colors.ts— taxonomy/category → color registry (the only place these colors are decided).
Principle: one token system + one primitive per role; warm neutrals; semantic colors with fixed meaning; category colors only through a registry; a generic table, one shell, one toast — guarded by a CI gate. Restraint over decoration. Density over whitespace.