StyleProvider
Sets the skin, the color mode and the brand color for everything inside it. Put one at the root of your app, and nest more for islands that look different, like a dark card or a tenant's brand.
Installation
Set up Camaleo once (Installation) and import the stylesheet of every skin you render, islands included. A skin without its CSS falls back to the defaults.
import { StyleProvider, useStyleScope, PortalScope, Portal, STYLE_NAMES } from "@camaleo/react";For the whole page, a Server Component can spread styleScopeProps({ styleName, mode, brand }) on <html> instead: same attributes, no client code. See Installation for the CSS and the fonts.
Anatomy
StyleProvider renders a <div> with data-style, data-mode and the brand tokens as inline variables. Every [data-style] element redeclares all the tokens and paints its own background, text color and font, so a nested island starts clean instead of inheriting its parent's skin. The mode and the brand, on the other hand, flow down until an island sets its own.
Popups (Menu, Select, Popover, Dialog, Drawer…) render in <body>, outside every provider. They wrap their content in a portal-scope element that copies the scope of the provider they were opened from.
Examples
Every preview already sits inside a StyleProvider: the one the bar above controls. These examples nest a second one, so you can see an island next to the page around it.
The Sunday Letter
Five stories about the city, picked by our editors and delivered at 7 a.m.
Workspace skin: minimal.
All skins side by side
Click a tile. A theme picker shows each choice in its own island, so people see the skin before they commit to it. Mode and brand still come from the page.
Invoice #1042
Due on November 15, 2026
Switch skin at runtime
Pick a skin and flip the switch. New props only swap attributes, so nothing remounts and the state inside survives.
Sol Bakery
Sourdough and pastries, 12 Oak Street
Vida Clinic
Family doctors, 480 Park Avenue
A brand per tenant
Set only brand to give each customer their color. Pass the page's skin from useStyleScope(), because styleName doesn't inherit.
Popups keep the island's style
Open the menu. It renders in <body>, yet it keeps the island's skin, mode and brand, because every popup re-applies the scope it was opened from.
Your own portal
Click Pay. Portal moves content to another node and keeps the island's style; use it, or wrap your own createPortal in PortalScope.
- Skin
minimal- Mode
light- Brand
#3fa34d
Read the current scope
useStyleScope() returns the nearest provider's props. Use it to pass the skin on, or to color a chart or canvas that can't read CSS.
Hard brand colors stay readable
Yellow, pale lime, near black: the brand scale picks black or white text for every step, so labels keep at least 4.5:1 in any skin and mode.
In context
These are whole pieces of UI you can copy, with an island inside the skin you picked.
Appearance
Applies to your account on every device.
Your 2026 in numbers
Thank you for a great year. Here is what your store did since January.
Vida Clinic
Pick a time for Thursday, November 12.
Guidelines
Good morning, Ana. You have 3 orders to ship today.
What's new in October
Bulk refunds, a faster search and dark mode for invoices.
A news panel, a campaign or an embedded widget can have its own identity. The rest of the page keeps one skin.
Three fields in three skins look broken, and people stop trusting the form.
Upgrade to Team and invite up to 20 people.
styleName={useStyleScope()?.["data-style"]} with mode="dark" gives a dark card in the page's own skin.
Upgrade to Team and invite up to 20 people.
Without styleName the island falls back to minimal. Switch the skin in the bar above: this card never changes.
Props
minimal.
Load the skin's stylesheet too (@camaleo/styles/<name>.css). A skin of your own works
too: any name your CSS targets with [data-style="<name>"].light, dark or system. Omit to inherit the mode from the parent scope (the root follows the system).All <div> props pass through; style merges with the brand variables. useStyleScope() returns { "data-style", "data-mode"?, style? } or null outside a provider. PortalScope takes only children. Portal takes children and container (default document.body). styleScopeProps(options) returns the same attributes for a Server Component.
Styling hooks
The provider is the source of the tokens every component reads. Target its attributes to change a skin, or set a token on an island to change only that section.
Attributes
Tokens it reads
/* rounder controls, only in the glass skin */
[data-style="glass"] {
--cml-radius-control: 14px;
}
/* a quieter surface for one island */
.help-center[data-style] {
--cml-surface: light-dark(var(--cml-brand-50), var(--cml-brand-950));
}Accessibility
- Without mode, the root follows prefers-color-scheme through CSS alone (color-scheme and light-dark()), so there is no flash and no script before hydration.
- Text, focus rings and control borders pass WCAG 2.2 AA in all 13 skins, light and dark, with any brand color. The library tests this in CI.
- Forced colors (Windows high contrast) replace the skin: components keep a transparent border and an outline for focus, so they stay visible.
- Reduced motion calms every skin's transitions, and reduced transparency makes glass opaque. Both are read from the system, per island too.