Theming
Load PyColors tokens, understand their Tailwind v4 mappings, and keep UI primitives consistent across light and dark themes.
Give every primitive the same semantic themeLink to section
@pycolors/ui primitives use semantic roles such as background, card,
primary, border, and ring. Load those roles once with
@pycolors/tokens, then let components consume them through Tailwind utilities.
Core idea
The token package owns theme values and Tailwind mappings. UI primitives
consume the resulting semantic utilities. Your application owns when the
dark class is applied.
Install and load the tokensLink to section
Install the UI and token packages if they are not already dependencies of your app.
pnpm add @pycolors/ui @pycolors/tokensImport the public token stylesheet once from your global stylesheet. In a Tailwind CSS v4 app, keep both imports at the top of that stylesheet.
@import "tailwindcss";
@import "@pycolors/tokens/tokens.css";Do not copy tokens.css into your application or import a path inside the
package. The supported public stylesheet is
@pycolors/tokens/tokens.css.
Understand the token chainLink to section
The token stylesheet connects four layers:
| Layer | Card example | Responsibility |
|---|---|---|
| Semantic CSS variable | --card | Defines the current theme value for a card surface. |
| Tailwind v4 mapping | --color-card: var(--card) | Exposes the role to Tailwind through @theme inline. |
| Semantic utility | bg-card | Lets application and component code request that role. |
| UI primitive | Card | Uses the shared role without owning a theme value. |
The package includes mappings like these:
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-border: var(--border);
--color-ring: var(--ring);
}Tailwind v4 turns those mappings into utilities such as bg-background,
text-foreground, bg-card, text-card-foreground, bg-primary,
border-border, and ring-ring. Changing a theme value therefore does not
require rewriting component classes.
Use semantic roles in product UILink to section
Import primitives from the public @pycolors/ui entry point and keep local
composition token-driven.
import {
Button,
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@pycolors/ui";
export function AccountSummary() {
return (
<Card className="border-border bg-card text-card-foreground">
<CardHeader>
<CardTitle>Workspace theme</CardTitle>
<CardDescription>
Components follow the semantic theme selected by your app.
</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
Surfaces, text, borders, and focus rings update together.
</p>
</CardContent>
<CardFooter>
<Button>Save preference</Button>
</CardFooter>
</Card>
);
}The example uses semantic utilities rather than raw color values. The same component remains valid in both themes.
Generate a semantic starting pointLink to section
PyColors Theme Builder generates a client-side, contrast-aware starting set of the same semantic CSS variables. Use it when you want to begin from a brand color without creating a new token contract.
Keep the integration order unchanged:
- import
@pycolors/tokens/tokens.cssonce; - place the generated overrides after that import;
- use semantic utilities and public
@pycolors/uiimports; and - verify the complete interface in both light and dark modes.
The generated overrides work with this package's existing @theme inline
bridge. They do not define a second Tailwind theme. Theme Builder output is
contrast-aware rather than an accessibility certification, so review brand
perception, focus, hover, disabled, color-vision, and complete page contexts
before shipping.
Understand the color checksLink to section
Open Color readability to see which text and controls may be difficult to read or identify. Results are grouped into Light mode and Dark mode, with examples of the actual generated colors and a suggested adjustment for each check that needs review.
Technical details lists the affected CSS variables, measured contrast, target ratio, and automatic color adjustments. For precise fixes, adjust those variables after exporting your theme. These checks cover generated colors; review the complete interface and its interactive states before shipping. Borders that are needed to identify controls deserve particular attention; decorative dividers can remain subtle. See the W3C guidance on control boundaries.
Preview typography with your colorsLink to section
Use the preview tabs to explore the same generated theme in three ways:
- Dashboard shows a complete example workspace.
- Components lets you try forms, buttons, checkboxes, feedback, and typography.
- Palette displays 12 accent and 12 neutral shades for the selected mode. Select a shade or named color role to inspect and copy its HEX or OKLCH value.
The Light background field controls the light-mode page background. Clear it to let the engine generate that value. The dark-mode background remains generated. Color values shown in the palette come directly from the same engine result as the exports; named roles under Colors by purpose show how those colors are used in your theme.
Theme Builder includes the same ten color presets as Starter Pro, with Monochrome and PyColors first, and a Font family selector for Geist, System, Geist Mono, Mona Sans, and Plus Jakarta Sans. Use Full screen to inspect the dashboard with a wider navigation sidebar. Your selected font, light/dark mode, activity period, and project filters remain selected when you close the preview. Dashboard content is demo data.
The site's PyColors / Monochrome selector uses the same color values as Starter Pro across marketing pages, the blog and documentation. Light, dark and system appearance remain independent of your palette choice. The other presets are available in Theme Builder and the Starter Pro demo. Preview settings stay inside the preview and do not change the site's appearance.
The selected preview tab, palette selection, and component demo controls also survive opening and closing full screen. Demo form values are only for exploring components; configuration files save theme settings, not demo content.
Use Copy typography CSS or Download typography CSS in the export section
to save the font configuration
separately from the CSS, Tailwind v4, or JSON color output. For a bundled font, download the provided asset and license, save it at the indicated public/fonts
path, and place the typography CSS after your token imports. System
needs no font file and uses the fonts available on each visitor's device. Selecting
a font does not change the color tokens or their contrast results.
Download and reopen your themeLink to section
In Export your theme, choose CSS, Tailwind v4, or JSON, then use Download to save the displayed format. Color files include both light and dark modes. Typography stays in a separate CSS file.
Use Save configuration to download a .pycolors-theme.json file containing
your color inputs, theme name, preset, font, radius, preview mode and dashboard settings. Later, use
Open configuration to restore those settings and regenerate the theme.
New files use version 2; existing version 1 files remain readable, including
legacy System Sans/System Mono font identifiers.
Configuration files are checked before applying them; an invalid or unsupported
file leaves your current settings unchanged. The JSON color-token export is a
different format and cannot be reopened as an editable configuration.
Files are processed locally without an account or uploads. There is no automatic save across page reloads: keep the configuration file to continue later.
Shared appearance and Starter ProLink to section
Named presets use the same semantic light/dark tokens in the builder and Starter Pro. Editing a brand or neutral color creates a generated custom theme. Editing only the light background retains the preset's dark colors. Contrast evidence is recalculated for the resulting colors.
The Radius control uses None / SM / MD / LG / XL. Dashboard preview adds scale (100%, 110%, 125%), density, content width, sidebar display/style/background, header, canvas and motion. These settings affect the preview, independently of the surrounding website. Font and radius CSS is exported separately from colors.
Choose Download Starter Pro config to generate appearance.ts. Replace
config/appearance.ts in your starter and restart it. In a browser with existing
preferences, use Customize → Reset to restore the new product defaults. Both
color modes and the selected appearance settings are included; no private package
or Theme Builder runtime is needed by the buyer's application.
Apply dark mode at the app shellLink to section
@pycolors/tokens defines its dark values under a .dark selector. Add the
class to the document root or app shell through your existing theme logic.
export function DocumentShell({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className="dark">
<body>{children}</body>
</html>
);
}Remove the class for the default light theme. A real application should derive that class from its existing preference, storage, or server-rendered theme strategy.
Application responsibility
PyColors provides the .dark token values. It does not provide a theme
toggle, persistence API, React provider, or flash-prevention strategy. Keep
those decisions in your application.
Customize without creating theme driftLink to section
Start with the existing semantic roles. If your product needs different theme values, change them once in the application's global theme layer after the package import, and review the corresponding light and dark roles together.
Keep these boundaries explicit:
- preserve role names such as
primary,card,destructive, andring; - keep component code on semantic utilities instead of palette or raw values;
- verify text contrast, focus visibility, disabled states, and status feedback in both themes;
- do not edit files in
node_modulesor import package internals; - do not scatter token redefinitions across components or feature stylesheets;
- treat a new public role as a design-system decision, not a local component workaround.
Prefer
- one global import of
@pycolors/tokens/tokens.css - semantic utilities such as
bg-cardandtext-muted-foreground - the
.darkclass controlled by application theme logic - central theme changes reviewed in light and dark mode
Avoid
- raw color values in reusable components
- component-local copies of semantic variables
- private imports from the UI or token packages
- using color alone to communicate status or errors
Verification checklistLink to section
- The token stylesheet is imported once from its public path.
- Tailwind v4 recognizes semantic utilities from the token mappings.
- UI imports use the public
@pycolors/uientry point. - Reusable components contain no raw theme colors.
- The app shell adds and removes
.darkthrough existing theme logic. - Text, controls, focus rings, and feedback remain understandable in both themes.