Design System
We use shadcn/ui components (style base-nova, see packages/ui/components.json). The shared components live in packages/ui and are imported as @iterate-com/ui/components/* — never copy them locally. Add new shadcn components inside packages/ui with npx shadcn@latest add <component>.
import { Button } from "@iterate-com/ui/components/button";
import { Field, FieldDescription, FieldGroup, FieldLabel } from "@iterate-com/ui/components/field";App Consistency#
Layout patterns, component usage, and structural patterns for the OS app live in apps/os. Look at existing routes in apps/os/src/routes before inventing new patterns.
Data fetching#
Avoid useEffect and useState for async work. Use @tanstack/react-query instead.
Use useSuspenseQuery sparingly — only when the whole component is meaningless without the data. Prefer useQuery with an isPending / null check when possible.
Mobile-First Development#
All UI must work on mobile first. Design for 375px width, then expand to desktop.
Page Titles#
Don't add page titles (h1) — the breadcrumbs in the header (PathBreadcrumbs in apps/os) provide context.
Responsive Padding#
Use p-4 md:p-8 for page containers, never fixed p-8.
Data Lists#
Use card layout for data lists (not tables). Cards work well on all screen sizes:
<div className="flex flex-col gap-3">
{items.map((item) => (
<div className="flex items-start justify-between gap-4 p-4 border rounded-lg bg-card">
<div className="flex min-w-0 flex-1 flex-col gap-1">
<div className="flex items-center gap-2">
<Circle className="size-2 fill-green-500 text-green-500" />
<span className="font-medium truncate">{item.name}</span>
</div>
<div className="text-sm text-muted-foreground">
{item.type} · {item.date}
</div>
</div>
<Button variant="ghost" size="icon" className="shrink-0">
...
</Button>
</div>
))}
</div>Key patterns:
flex flex-col gap-*for stacking — neverspace-y-*/space-x-*(see.agents/skills/shadcn/SKILL.md)flex items-start justify-between gap-4 p-4for card layoutmin-w-0 flex-1on content to enable truncation- Status dots with
Circleicon + fill color - Text metadata instead of badges (cleaner look)
Sheet over Dialog#
Prefer Sheet (slides in from side) over Dialog (modal popup) for forms and actions. Sheets are more mobile-friendly and feel more native.
Flex Layouts#
Use flex flex-col gap-4 sm:flex-row sm:items-center for layouts that should stack on mobile.
Core Principles#
- Use theme colors from the Tailwind theme (
bg-card,text-muted-foreground, …) — no random colors or gradients - Don't repeat bland copy in multiple places to "fill space"
- Prefer toast notifications over inline success/error messages
Component Reference#
Item#
Use the Item component (@iterate-com/ui/components/item) for lists, grids, or table-like layouts instead of raw <div>s.
Docs: https://ui.shadcn.com/docs/components/item
Note: ItemDescription has a default line-clamp-2. For longer content, use Card instead.
Card#
Use Card (@iterate-com/ui/components/card) for rich, multi-paragraph content. It has a size prop ("default" | "sm") — no variant prop.
Field Components#
Use for consistent form layouts and accessibility:
import {
Field,
FieldDescription,
FieldGroup,
FieldLabel,
FieldLegend,
FieldSet,
} from "@iterate-com/ui/components/field";
<FieldGroup>
<FieldSet>
<FieldLegend>Contact Information</FieldLegend>
<Field>
<FieldLabel htmlFor="name">Full Name</FieldLabel>
<Input id="name" placeholder="John Doe" required />
</Field>
<Field>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" type="email" />
<FieldDescription>We'll never share your email</FieldDescription>
</Field>
</FieldSet>
</FieldGroup>;Use Accordion for advanced/optional fields or multi-stage forms.
Empty#
Use the Empty family (Empty, EmptyHeader, EmptyTitle, EmptyDescription from @iterate-com/ui/components/empty) for empty states.
Sonner (Toast)#
Use toast from @iterate-com/ui/components/sonner for notifications — the Toaster is already set up.
import { toast } from "@iterate-com/ui/components/sonner";
// Prefer this
toast.success("Settings updated successfully");
toast.error("Failed to save changes");
// Avoid inline message state
const [successMessage, setSuccessMessage] = useState(null);Tabs#
Use Tabs, TabsList, TabsTrigger, TabsContent for tabbed interfaces.
Spinner#
Use Spinner for loading states - it has proper accessibility attributes.
Tooltip#
Use Tooltip components for contextual help on hover.
Accordion#
Use for collapsible content sections, optional form fields, or multi-stage forms.
Checkbox#
Use the Checkbox component with Field for proper form integration:
<Field orientation="horizontal">
<Checkbox id="newsletter" />
<FieldLabel htmlFor="newsletter" className="font-normal">
Subscribe to newsletter
</FieldLabel>
</Field>