Usage Patterns
Practical ways to import, compose, and ship PyColors UI primitives in real SaaS interfaces without changing the public API.
Use primitives as product building blocksLink to section
PyColors UI primitives are intentionally small. They give you accessible, token-driven pieces for SaaS interfaces, but your application owns the product state, data fetching, routing, validation, and business rules.
Use this page when you know which components you need and want to compose them into a real screen without reading package source.
Core idea
Import public primitives from @pycolors/ui, keep product logic in your app,
and compose behavior at the page, feature, or pattern layer.
Required usage rulesLink to section
These rules are part of the supported usage contract.
- Import from the public package entry point:
@pycolors/ui. - Keep
@pycolors/uiprimitives app-agnostic. Do not put auth, billing, routing, or product-specific decisions inside shared UI components. - Preserve native semantics: buttons for actions, links for navigation, labels for form controls, and table markup for tabular data.
- Add a client boundary only where your app uses browser-only behavior, local state, event handlers, or interactive Radix composition.
- Use existing component docs for exact props and variants before inventing a local wrapper.
- Keep accessibility responsibilities visible when composing overlays, forms, feedback, and dense data views.
Recommended defaultsLink to section
These defaults are not hard requirements, but they keep product surfaces consistent.
- Start with
Button,Input,Card,Alert, andTablebefore adding local abstractions. - Use
Dialogfor focused decisions and short forms. - Use
Sheetfor side workflows that should keep page context visible. - Use
Tabsfor switching between related panels, not for primary navigation. - Use
Toastfor non-blocking feedback after actions. - Use
Alertfor inline, persistent feedback that affects the current task. - Use
Paginationwhen the user needs stable page position and predictable record counts.
Import from the public entry pointLink to section
import {
Alert,
AlertDescription,
AlertTitle,
Button,
Checkbox,
CheckboxField,
CheckboxLabel,
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
Input,
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
Toast,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@pycolors/ui";Do not import from @pycolors/ui/src/* or @pycolors/ui/dist/*.
Those paths are internal package details and are not part of the public
contract.
Server and client component usageLink to section
Many PyColors primitives can be composed in a server-rendered page when you pass static content and no event handlers. The moment your file owns state, effects, or event handlers, move that composition to a small client component.
import { Alert, AlertDescription, AlertTitle, Button } from "@pycolors/ui";
export function BillingSummary() {
return (
<section className="space-y-4">
<Alert>
<AlertTitle>Plan active</AlertTitle>
<AlertDescription>
Your workspace is covered until the next renewal.
</AlertDescription>
</Alert>
<Button asChild>
<a href="/settings/billing">Manage billing</a>
</Button>
</section>
);
}Use a client boundary for event-driven composition.
"use client";
import {
Button,
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@pycolors/ui";
export function DeleteProjectDialog() {
function deleteProject() {
// Call your action, mutation, or route handler here.
}
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive">Delete project</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete project?</DialogTitle>
<DialogDescription>
This cannot be undone. Export anything you need first.
</DialogDescription>
</DialogHeader>
<Button variant="destructive" onClick={deleteProject}>
Confirm deletion
</Button>
</DialogContent>
</Dialog>
);
}Client boundary rule
Make the smallest interactive component a client component. Keep surrounding pages and data loading server-side when possible.
Forms and settingsLink to section
Use forms when the user is editing product state. Keep labels, descriptions, and validation messages close to the control they explain.
import {
Button,
Checkbox,
CheckboxField,
CheckboxLabel,
Input,
} from "@pycolors/ui";
export function NotificationSettings() {
return (
<form className="space-y-4">
<Input
id="billing-email"
label="Billing email"
description="Invoices and payment notices are sent here."
type="email"
/>
<CheckboxField>
<Checkbox id="weekly-summary" />
<CheckboxLabel htmlFor="weekly-summary">
Send a weekly workspace summary
</CheckboxLabel>
</CheckboxField>
<Button type="submit">Save settings</Button>
</form>
);
}Accessibility expectations:
- Every form control needs a visible label or equivalent accessible name.
- Validation errors should be text, not color alone.
- Disabled controls should still communicate why the action is unavailable when that reason matters to the workflow.
OverlaysLink to section
Choose overlays based on the job.
| Need | Primitive |
|---|---|
| Confirm or complete a focused task | Dialog |
| Work through a side panel without losing page context | Sheet |
| Pick a compact row or menu action | DropdownMenu |
"use client";
import {
Button,
Checkbox,
CheckboxField,
CheckboxLabel,
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@pycolors/ui";
export function FiltersSheet() {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Filters</Button>
</SheetTrigger>
<SheetContent side="right">
<SheetHeader>
<SheetTitle>Filter customers</SheetTitle>
<SheetDescription>
Narrow the table without leaving the current view.
</SheetDescription>
</SheetHeader>
<div className="mt-6 space-y-3">
<CheckboxField>
<Checkbox id="trialing" />
<CheckboxLabel htmlFor="trialing">Trialing</CheckboxLabel>
</CheckboxField>
<CheckboxField>
<Checkbox id="past-due" />
<CheckboxLabel htmlFor="past-due">Past due</CheckboxLabel>
</CheckboxField>
</div>
</SheetContent>
</Sheet>
);
}Overlay accessibility expectations:
- The trigger must have clear text or an accessible label.
- Dialog and Sheet titles should describe the task.
- Escape and focus-return behavior should remain intact.
- Do not place long multi-step workflows in a modal when a page would be clearer.
Product sections with TabsLink to section
Use Tabs to switch between related panels inside the same page.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@pycolors/ui";
export function SettingsTabs() {
return (
<Tabs defaultValue="profile">
<TabsList aria-label="Settings sections">
<TabsTrigger value="profile">Profile</TabsTrigger>
<TabsTrigger value="billing">Billing</TabsTrigger>
<TabsTrigger value="security">Security</TabsTrigger>
</TabsList>
<TabsContent value="profile">Profile form</TabsContent>
<TabsContent value="billing">Billing controls</TabsContent>
<TabsContent value="security">Security settings</TabsContent>
</Tabs>
);
}Use navigation links instead of tabs when the destination is a different route, bookmarkable page, or primary information architecture.
Tables and paginationLink to section
Use Table for comparable records. Add Pagination when the result set is
split into stable pages.
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@pycolors/ui";
const members = [
{ id: "m_1", name: "Ava Martin", role: "Owner" },
{ id: "m_2", name: "Noah Chen", role: "Member" },
];
export function MembersTable() {
return (
<section className="space-y-4">
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Role</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{members.map((member) => (
<TableRow key={member.id}>
<TableCell>{member.name}</TableCell>
<TableCell>{member.role}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Pagination aria-label="Members pages">
<PaginationContent>
<PaginationItem>
<PaginationPrevious disabled />
</PaginationItem>
<PaginationItem>
<PaginationLink isActive>1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext />
</PaginationItem>
</PaginationContent>
</Pagination>
</section>
);
}Accessibility expectations:
- Keep column headers meaningful.
- Use captions or nearby headings when the table purpose is not obvious.
- Keep pagination controls keyboard-operable and labeled for the collection they control.
Feedback patternsLink to section
Use inline feedback when the message affects the current surface. Use toast feedback when the message confirms a completed action without blocking the user.
import { Alert, AlertDescription, AlertTitle } from "@pycolors/ui";
export function PaymentIssueAlert() {
return (
<Alert variant="destructive">
<AlertTitle>Payment failed</AlertTitle>
<AlertDescription>
Update your payment method to keep the workspace active.
</AlertDescription>
</Alert>
);
}"use client";
import {
Button,
Toast,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@pycolors/ui";
export function SaveToastExample() {
return (
<ToastProvider>
<Button>Save changes</Button>
<Toast open variant="success">
<ToastTitle>Settings saved</ToastTitle>
<ToastDescription>
Your workspace preferences were updated.
</ToastDescription>
</Toast>
<ToastViewport />
</ToastProvider>
);
}Common mistakesLink to section
Prefer
- importing public primitives from
@pycolors/ui - using
Button asChildwhen a link should look like a button - keeping state, mutations, and routing in app code
- using
Alertfor persistent task feedback - using
Toastfor non-blocking action confirmation - using
Tableonly for structured, comparable records
Avoid
- importing from
@pycolors/ui/src/*or@pycolors/ui/dist/* - using a clickable
divinstead of a real button or link - putting billing, auth, or product rules inside UI primitives
- using a toast for a blocking error the user must fix now
- using tabs for primary route navigation
- hiding labels or validation behind color alone
Related docsLink to section
Next step
Start with the component docs for exact props, then use Patterns when the interaction needs state, async behavior, or repeated product decisions.