UIUpdated August 2, 2026

Migration Guidance

Adopt PyColors UI in an existing React or Next.js codebase without broad rewrites, private imports, or public API changes.

UIMigration guidance

Migrate one product surface at a timeLink to section

Use this guide when you already have a React or Next.js app and want to adopt @pycolors/ui without rewriting the whole interface.

The safest migration is incremental:

  1. confirm package, Tailwind, and token prerequisites
  2. replace private or local primitive imports with public @pycolors/ui imports
  3. migrate low-risk primitives first
  4. keep product state, routing, validation, and data loading in your app
  5. verify accessibility and regression behavior before moving to the next surface

Core idea

Treat PyColors UI as a primitive layer. Migrate imports and composition first; keep product behavior where it already belongs.

Before you migrateLink to section

Confirm these prerequisites before changing component imports.

CheckWhy it matters
@pycolors/ui is installedConsumers should import supported primitives from the package entry point.
React and React DOM are installedThey are peer dependencies of the UI package.
Tailwind utilities are availablePyColors UI components are token-driven and Tailwind-friendly.
PyColors token CSS is loaded where requiredComponents expect semantic design tokens such as bg-card, border-border, and text-muted-foreground.
Existing behavior is covered manually or by testsMigration should not change product flows unexpectedly.

Use the design-system docs for token setup and the component docs for exact props before replacing local abstractions.

Import only from the public packageLink to section

The supported public entry point is @pycolors/ui.

supported-imports.tsx
import {
  Alert,
  Button,
  Checkbox,
  Dialog,
  Input,
  Pagination,
  Sheet,
  Table,
  Tabs,
  Toast,
} from "@pycolors/ui";

Do not import from source, generated output, or mirrored repository internals.

Prefer

  • importing primitives from @pycolors/ui
  • moving one feature area at a time
  • keeping app-specific behavior in app code

Avoid

  • importing from @pycolors/ui/src/*
  • importing from @pycolors/ui/dist/*
  • rewriting every screen in one migration PR
replace-private-imports.tsx
// Before
import { Button } from "@/components/ui/button";

// After
import { Button } from "@pycolors/ui";

If your local component has product-specific behavior, keep that behavior in a local wrapper and move only the primitive rendering to PyColors UI.

keep-product-behavior-local.tsx
import { Button } from "@pycolors/ui";

export function BillingPortalButton() {
  return (
    <Button asChild>
      <a href="/settings/billing">Manage billing</a>
    </Button>
  );
}

Start with primitives that have low behavioral risk, then move to interactive composition.

OrderPrimitiveMigration note
1Button, Badge, Card, AlertLow-risk visual and semantic primitives.
2Input, CheckboxVerify labels, descriptions, errors, and form submission behavior.
3Table, Pagination, TabsKeep data fetching, routing, filtering, and page state outside the primitive.
4Dialog, Sheet, DropdownMenu, ToastAdd a client boundary and verify keyboard/focus behavior.

Do not migrate a whole product area until the previous migrated surface still passes user-visible behavior checks.

React Server Component boundariesLink to section

In a Next.js App Router app, keep pages and data loading as Server Components when possible. Add "use client" only to the smallest file that owns browser state, event handlers, effects, or interactive overlay/toast state.

server-rendered-status.tsx
import { Alert, AlertDescription, AlertTitle, Button } from "@pycolors/ui";

export function AccountStatus() {
  return (
    <section className="space-y-4">
      <Alert>
        <AlertTitle>Workspace ready</AlertTitle>
        <AlertDescription>
          Your setup is complete. Invite teammates when you are ready.
        </AlertDescription>
      </Alert>

      <Button asChild>
        <a href="/settings/team">Open team settings</a>
      </Button>
    </section>
  );
}

Use a client component when the migrated surface owns interaction state.

client-dialog-migration.tsx
"use client";

import {
  Button,
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@pycolors/ui";

export function ArchiveProjectDialog() {
  function archiveProject() {
    // Call your action, mutation, or route handler here.
  }

  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="outline">Archive project</Button>
      </DialogTrigger>

      <DialogContent>
        <DialogHeader>
          <DialogTitle>Archive project?</DialogTitle>
          <DialogDescription>
            The project will be hidden from active dashboards.
          </DialogDescription>
        </DialogHeader>

        <DialogFooter>
          <Button variant="destructive" onClick={archiveProject}>
            Archive
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

Client boundary rule

A component can import PyColors UI from a Server Component file when the composition is static. Add "use client" when your file adds browser-owned state or handlers.

Forms and validationLink to section

When migrating forms, preserve labels, descriptions, required state, and error messages. Do not replace form validation logic with UI primitives.

settings-form-migration.tsx
import {
  Button,
  Checkbox,
  CheckboxDescription,
  CheckboxField,
  CheckboxLabel,
  Input,
} from "@pycolors/ui";

export function NotificationSettingsForm() {
  return (
    <form className="space-y-4">
      <Input
        id="billing-email"
        label="Billing email"
        helperText="Invoices and payment notices are sent here."
        type="email"
      />

      <CheckboxField>
        <Checkbox id="weekly-summary" />
        <div>
          <CheckboxLabel htmlFor="weekly-summary">
            Send weekly workspace summary
          </CheckboxLabel>
          <CheckboxDescription>
            Includes usage, billing, and team activity.
          </CheckboxDescription>
        </div>
      </CheckboxField>

      <Button type="submit">Save settings</Button>
    </form>
  );
}

Regression checks:

  • submitting the form still calls the same action
  • required and disabled states still work
  • errors are announced as text, not color alone
  • labels and descriptions remain associated with the control

Tables and paginationLink to section

Keep data access and routing outside @pycolors/ui. Migrate the visible table structure first, then wire existing state back into the same product flow.

table-pagination-migration.tsx
import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@pycolors/ui";

export function MembersTable() {
  return (
    <div className="space-y-4">
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>Name</TableHead>
            <TableHead>Role</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          <TableRow>
            <TableCell>Ada Lovelace</TableCell>
            <TableCell>Owner</TableCell>
          </TableRow>
        </TableBody>
      </Table>

      <Pagination>
        <PaginationContent>
          <PaginationItem>
            <PaginationLink isActive>1</PaginationLink>
          </PaginationItem>
          <PaginationItem>
            <PaginationLink>2</PaginationLink>
          </PaginationItem>
        </PaginationContent>
      </Pagination>
    </div>
  );
}

Review existing table behavior after migration:

  • column headers still describe the data
  • loading and empty states are still reachable
  • pagination state still matches routing or query parameters
  • row actions still use accessible buttons, links, dialogs, or menus

Tabs, sheets, and toastsLink to section

Interactive primitives should preserve keyboard behavior and focus ownership. Migrate the primitive structure without hiding product decisions inside shared UI components.

interactive-surface-migration.tsx
"use client";

import {
  Button,
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
  Tabs,
  TabsContent,
  TabsList,
  TabsTrigger,
  Toast,
  ToastDescription,
  ToastProvider,
  ToastTitle,
  ToastViewport,
} from "@pycolors/ui";

export function BillingWorkspace() {
  return (
    <ToastProvider>
      <Tabs defaultValue="overview">
        <TabsList>
          <TabsTrigger value="overview">Overview</TabsTrigger>
          <TabsTrigger value="invoices">Invoices</TabsTrigger>
        </TabsList>

        <TabsContent value="overview">
          <Sheet>
            <SheetTrigger asChild>
              <Button variant="outline">Open filters</Button>
            </SheetTrigger>
            <SheetContent>
              <SheetHeader>
                <SheetTitle>Invoice filters</SheetTitle>
                <SheetDescription>
                  Narrow the invoice list without leaving the page.
                </SheetDescription>
              </SheetHeader>
            </SheetContent>
          </Sheet>
        </TabsContent>
      </Tabs>

      <Toast variant="success" open>
        <ToastTitle>Billing updated</ToastTitle>
        <ToastDescription>Your changes were saved.</ToastDescription>
      </Toast>
      <ToastViewport />
    </ToastProvider>
  );
}

After migrating interactive surfaces, test:

  • Tab and arrow-key behavior where relevant
  • Escape closes overlays
  • focus returns to the trigger after closing
  • toast announcements match the urgency of the message
  • product actions still require the same confirmation or authorization

Migration checklistLink to section

Use this checklist for each migrated page, feature, or component group.

  • Imports come from @pycolors/ui.
  • No code imports from @pycolors/ui/src/* or @pycolors/ui/dist/*.
  • Existing product behavior is unchanged.
  • Labels, descriptions, errors, and disabled states are preserved.
  • Server/client boundaries are explicit and minimal.
  • Keyboard and focus behavior pass manual review.
  • Local wrappers contain only product behavior, not duplicated primitive styling.
  • Links to adjacent docs are updated when a new public pattern is documented.

What to do nextLink to section

Use the component reference pages when replacing a specific primitive, then use the usage-pattern and composition docs to migrate whole product surfaces.