Composition
How to combine PyColors UI primitives into real SaaS interfaces without creating fragile abstractions or inconsistent product surfaces.
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.
Pattern
A reusable product behavior made from multiple primitives, state, and UX rules.
Product surface
A real feature or page where UI, copy, data, auth, billing, and product decisions meet.
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.
| Area | Responsibility | Typical primitives |
|---|---|---|
| Header | Context and primary action | Button, Badge |
| Content | Main product information | Card, Table, Tabs |
| Feedback | Loading, empty, error, success | Skeleton, EmptyState, Alert, Toast |
| Overlays | Focused or progressive workflows | Dialog, 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.
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.
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.
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 need | Better as |
|---|---|
| Table with loading, empty, error, filters, actions | Data table pattern |
| Save / retry / undo / optimistic feedback | Async actions pattern |
| Dropdown vs Dialog vs Sheet decisions | Overlays pattern |
| Form structure with validation and error handling | Guide / Pattern |
| SaaS dashboard shell | Guide / 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
Understand the philosophy behind small primitives and product-level patterns.
Move from components to production-ready feature blocks.
Compose primitives into real dashboard structure.
See how UI composition connects to auth, billing, backend, and production flows.