Build a PyColors theme from your brand colors
Free, client-side, PyColors-compatible theme generation for production-shaped SaaS interfaces.
It runs locally in your browser. No account is required, and theme inputs or generated tokens are not persisted or transmitted.
PyColors Theme Studio
Deterministic semantic token engine
Local session
Northstar
Avery's workspace
Northstar workspace
Dashboard
A focused view of delivery, capacity, and client work.
Active projects
12
3 shipping this week
Team capacity
86%
4 seats available
Delivery health
98%
All client milestones on track
Plan status
HealthyPro
Renews Sep 14, 2026
Delivery board
Your most active client work.
| Project | Owner | Status | Updated |
|---|---|---|---|
OOOrbit onboarding | Avery Kim | On track | 12m ago |
APAtlas platform | Sam Patel | In review | 1h ago |
PLPulse launch | Morgan Lee | Planning | Yesterday |
Live activity
- Sam moved Atlas platform to review.
- Morgan invited two new collaborators.
- Billing report is ready to review.
Usage update
Form validation
Test field labels, errors, and helper text.
Use a public hostname before inviting clients.
Empty state
Test quiet surfaces without losing hierarchy.
No pending reviews
New client feedback will appear here.
Focus-ready action
Test the visible keyboard focus treatment.
Keep the annual plan on track
Add collaborator capacity before the next delivery cycle.
03 · Review the result
Theme quality
A short quality signal for the current theme. Details stay available when you need to review a real product context.
6 items to review
Automatic safeguards
6 safeguards were applied to keep the generated values usable.
See automatic adjustments
A neutral scale was generated from your brand.
A color was refined to display reliably.
Checked in light mode.
Text color was strengthened for readability.
Checked in light mode.
Text color was strengthened for readability.
Checked in light mode.
A color was refined to display reliably.
Checked in dark mode.
Text color was strengthened for readability.
Checked in dark mode.
Visual contrast check
6 token pairs need a visual check before shipping. This is evidence, not a failed export.
Review 6 measured ratios
Inspect borders, inputs, focus rings, and destructive actions in the preview that matches your product.
28 pairs meet their target.
lightborder / background
non text · needs 3:1
1.96:1
lightinput / background
non text · needs 3:1
1.96:1
darkdestructive-foreground / destructive
normal text · needs 4.5:1
2.75:1
darkring / background
non text · needs 3:1
2.89:1
darkborder / background
non text · needs 3:1
1.64:1
darkinput / background
non text · needs 3:1
1.64:1
04 · Export implementation
Token exports
Copy deterministic output from the current valid generated theme. Nothing is uploaded or saved.
CSS · Tailwind v4 · JSON
Load these semantic overrides after the PyColors token import.
Integration checklist
Apply CSS safely
Current export
- 1Keep the PyColors token import in your global stylesheet.
- 2Paste the generated override after that import so it wins predictably.
- 3Review one light and one dark product screen before committing.
Generated artifact
CSS implementation code
Inspect the exact serializer output only when you are ready to implement it.
Generated override example
:root {
--background: #fafafa;
--background: oklch(0.9851 0.0000 0.00);
--foreground: #121116;
--foreground: oklch(0.1800 0.0113 291.61);
/* Remaining semantic roles are included in the copied export. */
}
.dark {
--background: #060508;
--background: oklch(0.1200 0.0075 291.61);
--foreground: #f5f4fc;
--foreground: oklch(0.9700 0.0113 291.61);
/* Remaining semantic roles are included in the copied export. */
}CSS
Select the output or copy the complete generated artifact.
:root {
--background: #fafafa;
--background: oklch(0.9851 0.0000 0.00);
--foreground: #121116;
--foreground: oklch(0.1800 0.0113 291.61);
--card: #f2f1f8;
--card: oklch(0.9600 0.0095 291.61);
--card-foreground: #121116;
--card-foreground: oklch(0.1800 0.0113 291.61);
--popover: #f2f1f8;
--popover: oklch(0.9600 0.0095 291.61);
--popover-foreground: #121116;
--popover-foreground: oklch(0.1800 0.0113 291.61);
--primary: #6a30d4;
--primary: oklch(0.4904 0.2293 291.61);
--primary-foreground: #fcfbff;
--primary-foreground: oklch(0.9900 0.0051 291.61);
--secondary: #e3e2eb;
--secondary: oklch(0.9151 0.0120 291.61);
--secondary-foreground: #121116;
--secondary-foreground: oklch(0.1800 0.0113 291.61);
--muted: #e3e2eb;
--muted: oklch(0.9151 0.0120 291.61);
--muted-foreground: #121116;
--muted-foreground: oklch(0.1800 0.0113 291.61);
--accent: #e3e0f3;
--accent: oklch(0.9151 0.0252 291.61);
--accent-foreground: #121116;
--accent-foreground: oklch(0.1800 0.0113 291.61);
--destructive: #e40016;
--destructive: oklch(0.5770 0.2354 27.32);
--destructive-foreground: #fafafa;
--destructive-foreground: oklch(0.9850 0.0000 0.00);
--success: #16a34a;
--success: oklch(0.6271 0.1699 149.21);
--success-foreground: #09120b;
--success-foreground: oklch(0.1700 0.0200 152.00);
--warning: #e5c057;
--warning: oklch(0.8200 0.1300 90.00);
--warning-foreground: #201a09;
--warning-foreground: oklch(0.2200 0.0300 90.00);
--border: #b6b4c0;
--border: oklch(0.7752 0.0170 291.61);
--border-subtle: #ceccd7;
--border-subtle: oklch(0.8501 0.0145 291.61);
--border-strong: #838190;
--border-strong: oklch(0.6103 0.0220 291.61);
--input: #b6b4c0;
--input: oklch(0.7752 0.0170 291.61);
--ring: #6a30d4;
--ring: oklch(0.4904 0.2293 291.61);
--surface: #fcfbff;
--surface: oklch(0.9900 0.0051 291.61);
--surface-foreground: #121116;
--surface-foreground: oklch(0.1800 0.0113 291.61);
--surface-muted: #f2f1f8;
--surface-muted: oklch(0.9600 0.0095 291.61);
--surface-elevated: #f2f1f8;
--surface-elevated: oklch(0.9600 0.0095 291.61);
--surface-inverted: #121116;
--surface-inverted: oklch(0.1800 0.0113 291.61);
--surface-inverted-foreground: #fcfbff;
--surface-inverted-foreground: oklch(0.9900 0.0051 291.61);
}
.dark {
--background: #060508;
--background: oklch(0.1200 0.0075 291.61);
--foreground: #f5f4fc;
--foreground: oklch(0.9700 0.0113 291.61);
--card: #0d0c10;
--card: oklch(0.1570 0.0095 291.61);
--card-foreground: #f5f4fc;
--card-foreground: oklch(0.9700 0.0113 291.61);
--popover: #0d0c10;
--popover: oklch(0.1570 0.0095 291.61);
--popover-foreground: #f5f4fc;
--popover-foreground: oklch(0.9700 0.0113 291.61);
--primary: #6a30d4;
--primary: oklch(0.4904 0.2293 291.61);
--primary-foreground: #f5f4fc;
--primary-foreground: oklch(0.9700 0.0113 291.61);
--secondary: #17161c;
--secondary: oklch(0.2052 0.0120 291.61);
--secondary-foreground: #f5f4fc;
--secondary-foreground: oklch(0.9700 0.0113 291.61);
--muted: #17161c;
--muted: oklch(0.2052 0.0120 291.61);
--muted-foreground: #f5f4fc;
--muted-foreground: oklch(0.9700 0.0113 291.61);
--accent: #1a093b;
--accent: oklch(0.2052 0.0894 291.61);
--accent-foreground: #f5f4fc;
--accent-foreground: oklch(0.9700 0.0113 291.61);
--destructive: #ff6568;
--destructive: oklch(0.7040 0.1877 22.22);
--destructive-foreground: #fafafa;
--destructive-foreground: oklch(0.9850 0.0000 0.00);
--success: #22c55e;
--success: oklch(0.7227 0.1920 149.58);
--success-foreground: #040b06;
--success-foreground: oklch(0.1400 0.0200 152.00);
--warning: #e5c057;
--warning: oklch(0.8200 0.1300 90.00);
--warning-foreground: #201a09;
--warning-foreground: oklch(0.2200 0.0300 90.00);
--border: #35333d;
--border: oklch(0.3274 0.0170 291.61);
--border-subtle: #25242c;
--border-subtle: oklch(0.2645 0.0145 291.61);
--border-strong: #514f5d;
--border-strong: oklch(0.4349 0.0220 291.61);
--input: #35333d;
--input: oklch(0.3274 0.0170 291.61);
--ring: #6a30d4;
--ring: oklch(0.4904 0.2293 291.61);
--surface: #060508;
--surface: oklch(0.1200 0.0075 291.61);
--surface-foreground: #f5f4fc;
--surface-foreground: oklch(0.9700 0.0113 291.61);
--surface-muted: #0d0c10;
--surface-muted: oklch(0.1570 0.0095 291.61);
--surface-elevated: #0d0c10;
--surface-elevated: oklch(0.1570 0.0095 291.61);
--surface-inverted: #f5f4fc;
--surface-inverted: oklch(0.9700 0.0113 291.61);
--surface-inverted-foreground: #060508;
--surface-inverted-foreground: oklch(0.1200 0.0075 291.61);
}
05 · Continue with PyColors
Continue with generated tokens
Copy the current output, then follow the supported PyColors integration path.
Apply generated overrides in the supported order
- Install and use
@pycolors/tokens. - Import
@pycolors/tokens/tokens.cssonce in your global stylesheet. - Place the generated CSS overrides after that token import.
- Keep application code on semantic utilities and public
@pycolors/uiimports. - Verify the complete interface in both light and dark modes.
The generated overrides use the existing Tailwind v4 @theme inline bridge in the token package; they do not create a second Tailwind theme contract. Read the theming guide for the supported integration boundary.
Review the generated theme in context
Theme Builder is contrast-aware, not an accessibility certification. Review generated values in the real interface before shipping.
- Brand perception across your product and audience.
- Complete component and page context.
- Color-vision deficiencies and non-color cues.
- States communicated only by color.
- Real focus, hover, and disabled contexts.