UIUpdated April 28, 2026

Composition

How to combine PyColors UI primitives into real SaaS interfaces without creating fragile abstractions or inconsistent product surfaces.

UIComposition

Compose product surfaces from small primitivesLink to section

PyColors UI components are intentionally small.

They are not designed to own your whole product flow.

They are designed to be composed into clear, maintainable product surfaces:

  • dashboards
  • settings pages
  • billing panels
  • forms
  • tables
  • overlays
  • admin tools
  • empty and error states

The goal is not to create the biggest component possible.

The goal is to compose smaller primitives into interfaces that remain understandable as the product grows.

Core idea

Components should stay small. Product meaning comes from composition.

Why composition mattersLink to section

Most UI systems start clean and become fragile later.

The usual cause is not the design.

It is the abstraction strategy.

Products become hard to maintain when teams:

  • create one giant component for every feature
  • duplicate the same layout pattern across pages
  • mix business logic inside UI primitives
  • add one-off variants instead of composing states
  • hide product decisions behind unclear props

Composition prevents that.

It lets you keep primitives reusable while still building real SaaS experiences.

The composition modelLink to section

PyColors uses a layered mental model.

Primitive

A small reusable component with one clear responsibility.

ButtonCardInputBadge

Pattern

A reusable product behavior made from multiple primitives, state, and UX rules.

Data tableOverlaysAsync actions

Product surface

A real feature or page where UI, copy, data, auth, billing, and product decisions meet.

DashboardBillingSettings

Rule of thumb

If the component describes visual structure, keep it in UI. If it describes product behavior, move it into Guides or Patterns.

Composition principlesLink to section

Structure first

Start with page structure, hierarchy, and content purpose before styling details.

Meaning over decoration

Use components to communicate product intent, not to decorate every surface.

Clear ownership

UI renders state. Product logic, auth, billing, and backend rules live outside primitives.

Page compositionLink to section

A production SaaS page usually has four responsibilities.

AreaResponsibilityTypical primitives
HeaderContext and primary actionButton, Badge
ContentMain product informationCard, Table, Tabs
FeedbackLoading, empty, error, successSkeleton, EmptyState, Alert, Toast
OverlaysFocused or progressive workflowsDialog, Sheet, DropdownMenu

This keeps the page readable.

The page coordinates product intent. Components render the pieces.

Example: settings pageLink to section

A settings page is not one component.

It is a composition of surfaces, navigation, inputs, and confirmations.

settings-page.tsx
export function SettingsPage() {
  return (
    <div className="space-y-6">
      <SettingsHeader />

      <Tabs defaultValue="profile">
        <TabsList>
          <TabsTrigger value="profile">Profile</TabsTrigger>
          <TabsTrigger value="security">Security</TabsTrigger>
          <TabsTrigger value="billing">Billing</TabsTrigger>
        </TabsList>

        <TabsContent value="profile">
          <ProfileSettingsCard />
        </TabsContent>

        <TabsContent value="security">
          <SecuritySettingsCard />
        </TabsContent>

        <TabsContent value="billing">
          <BillingSettingsCard />
        </TabsContent>
      </Tabs>
    </div>
  );
}

This page uses primitives, but the product meaning comes from the composition:

  • tabs separate concerns
  • cards group content
  • inputs collect values
  • dialogs confirm destructive actions
  • alerts communicate blocking states

Example: billing panelLink to section

Billing UI needs clear hierarchy because it affects trust and revenue.

billing-panel.tsx
export function BillingPanel() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>Current plan</CardTitle>
      </CardHeader>

      <CardContent className="space-y-4">
        <Badge variant="success">Active</Badge>
        <p className="text-sm text-muted-foreground">
          Your subscription is active and renews next month.
        </p>
        <Button>Manage billing</Button>
      </CardContent>
    </Card>
  );
}

The primitives stay simple.

The product layer decides:

  • what the billing state means
  • what action is available
  • what copy reassures the user
  • when access should be gated

Example: data-heavy pageLink to section

Data-heavy pages should not make the table responsible for everything.

projects-page.tsx
export function ProjectsPage() {
  return (
    <div className="space-y-6">
      <ProjectsHeader />
      <ProjectsToolbar />
      <ProjectsTable />
      <ProjectsPagination />
    </div>
  );
}

The table renders rows.

The page coordinates:

  • filters
  • loading state
  • empty state
  • error recovery
  • pagination
  • row actions

Why this matters

A table should not own your product flow. It should render structured data inside a larger page composition.

Composition workflowLink to section

Start with the user goalLink to section

Define what the page helps the user do.

Good composition starts with purpose, not components.

Choose the page structureLink to section

Decide the major regions: header, content, controls, feedback, overlays.

Use primitives for each responsibilityLink to section

Use Button for actions, Card for surfaces, Input for values, Badge for metadata, and Alert for blocking feedback.

Move repeated behavior into PatternsLink to section

If multiple pages repeat the same flow, document it as a pattern.

Keep business logic outside UI primitivesLink to section

Auth, billing, permissions, data fetching, and backend rules should not live inside low-level UI components.

When to extract a componentLink to section

Extract a component when:

  • the visual structure repeats
  • the component has a clear responsibility
  • it makes the page easier to read
  • the abstraction does not hide important product logic

Do not extract when:

  • the component exists only once
  • the abstraction creates too many props
  • the component mixes unrelated responsibilities
  • the name does not describe a clear concept

Extraction rule

Extract for clarity, not for cleverness.

When to create a PatternLink to section

Create a Pattern when the repeated need includes behavior.

Examples:

Repeated needBetter as
Table with loading, empty, error, filters, actionsData table pattern
Save / retry / undo / optimistic feedbackAsync actions pattern
Dropdown vs Dialog vs Sheet decisionsOverlays pattern
Form structure with validation and error handlingGuide / Pattern
SaaS dashboard shellGuide / Starter

A pattern can include multiple primitives, state rules, copywriting rules, and accessibility guidance.

Prefer / avoidLink to section

Prefer

  • small primitives with clear responsibility
  • page-level composition for product meaning
  • patterns for repeated behavior
  • semantic tokens over raw values
  • explicit structure over hidden magic

Avoid

  • giant configurable components
  • business logic inside primitives
  • duplicated product flows across pages
  • custom styles that bypass tokens
  • abstracting before the pattern is clear

Composition checklistLink to section

Before shipping a composed UI surface, confirm:

  • the page has a clear user goal
  • each component has one clear responsibility
  • semantic tokens are used for visual roles
  • loading, empty, and error states are handled
  • actions have clear feedback
  • overlays are used only when needed
  • repeated behavior is moved into a pattern
  • business logic is not hidden inside primitives
  • the layout still makes sense on mobile

Product quality rule

A premium UI is not more decorated. It is easier to understand, easier to maintain, and harder to misuse.

Next stepsLink to section