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 — never space-y-* / space-x-* (see .agents/skills/shadcn/SKILL.md)
  • flex items-start justify-between gap-4 p-4 for card layout
  • min-w-0 flex-1 on content to enable truncation
  • Status dots with Circle icon + 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>

Was this page helpful?