Theming
Colors, sizes, variants and other theming options.
Overview
Shim makes it easy to set the overall look and feel of your app: semantic
colors, preferred density, variants for key components, and flexible label
positions keep things consistent while staying easy to tweak. Apply size,
variant, and label choices per component or set defaults globally with the
Theme provider.
Colors
Shim uses Radix UI Colors for its color system. Radix provides a carefully designed color palette with automatic dark mode support and semantic scales that work across different use cases.
Shim provides five color intents, each with a full set of use-case tokens:
neutral- Gray tones for UI elementsaccent- Primary brand color (default: Iris)success- Positive states (default: Green)warning- Cautionary states (default: Orange)danger- Destructive actions and errors (default: Red)
Using color tokens
Reference color tokens in your Tailwind classes:
<div className="bg-neutral-panel text-neutral-text border border-neutral-line">
<p className="text-neutral-text-subtle">Subtle text</p>
<p className="text-accent-text-contrast">Contrast text</p>
<button className="bg-accent-bg hover:bg-accent-bg-hover">Action</button>
</div>Customizing colors
To change the colors, edit the Radix color imports and aliases in your
theme.css file. For example, to change the accent color to blue:
/* Import blue scales */
@import "@radix-ui/colors/blue.css";
@import "@radix-ui/colors/blue-alpha.css";
@import "@radix-ui/colors/blue-dark.css";
@import "@radix-ui/colors/blue-dark-alpha.css";
/* Then update the accent aliases in @theme */
@theme {
--color-accent-1: var(--blue-a1);
--color-accent-2: var(--blue-a2);
/* ... continue for all 12 steps */
}See the Radix Colors documentation for the full color palette and detailed usage guidance.
Size
Most components support four sizes (1, 2, 3, 4) via the size prop that
scale proportionally. Size can be set on individual components, on container
components like Form, Well, or Toolbar to affect their children, or via
the Theme provider to set a default for all components.
"use client";
import { ListIcon } from "@phosphor-icons/react";
import { Badge } from "@/shim-ui/badge";
import { Button } from "@/shim-ui/button";
import { ICON_SIZE_MAP } from "@/shim-ui/lib/theme";
import { Menu, MenuItem, MenuSeparator, MenuTrigger } from "@/shim-ui/menu";
import { Select, SelectItem } from "@/shim-ui/select";
import { Slider } from "@/shim-ui/slider";
import { Switch } from "@/shim-ui/switch";
import { TextField } from "@/shim-ui/text-field";
import { ToggleButton } from "@/shim-ui/toggle-button";
import { ToggleButtonGroup } from "@/shim-ui/toggle-button-group";
import { Well } from "@/shim-ui/well";
export default () =>
([1, 2, 3, 4] as const).map((size) => (
<Well key={size} size={size}>
<Badge className="aspect-square">{size}</Badge>
<div className="flex gap-[inherit]">
<Button className="flex-1">Let's Go</Button>
<MenuTrigger>
<Button aria-label="Menu" isIconOnly>
<ListIcon size={ICON_SIZE_MAP[size]} />
</Button>
<Menu>
<MenuItem>Edit</MenuItem>
<MenuItem>Duplicate</MenuItem>
<MenuSeparator />
<MenuItem intent="danger">Delete</MenuItem>
</Menu>
</MenuTrigger>
</div>
<ToggleButtonGroup defaultSelectedKeys={["grid"]}>
<ToggleButton id="grid">Grid</ToggleButton>
<ToggleButton id="list">List</ToggleButton>
</ToggleButtonGroup>
<Select defaultValue={"grid"}>
<SelectItem id="grid">Grid</SelectItem>
<SelectItem id="list">List</SelectItem>
</Select>
<TextField aria-label="TextField" defaultValue="Hello" />
<div className="flex gap-[inherit]">
<Slider
aria-label="Slider"
className="flex-1"
defaultValue={50}
isFilled
/>
<Switch aria-label="Switch" defaultSelected />
</div>
</Well>
));Variants
Field variants
Forms and field-based components support classic, soft, and outline
variants that adjust visual styles while preserving spacing and layout.
import { Badge } from "@/shim-ui/badge";
import { Checkbox, CheckboxGroup } from "@/shim-ui/checkbox";
import { NumberField } from "@/shim-ui/number-field";
import { Radio, RadioGroup } from "@/shim-ui/radio-group";
import { SearchField } from "@/shim-ui/search-field";
import { Slider } from "@/shim-ui/slider";
import { Switch } from "@/shim-ui/switch";
import { TextField } from "@/shim-ui/text-field";
import { Well } from "@/shim-ui/well";
export default () =>
(["classic", "soft", "outline"] as const).map((variant) => (
<Well className="grid grid-cols-4 gap-4" key={variant}>
<Badge className="col-span-full">{variant}</Badge>
<TextField
className="col-span-2"
defaultValue="Hello"
variant={variant}
/>
<NumberField className="col-span-2" defaultValue={0} variant={variant} />
<SearchField className="col-span-full" variant={variant} />
<Switch className="col-span-1" defaultSelected variant={variant} />
<Slider
className="col-span-3"
defaultValue={50}
isFilled
variant={variant}
/>
<RadioGroup className="col-span-2" defaultValue="1" variant={variant}>
<Radio value="1">Option 1</Radio>
<Radio value="2">Option 2</Radio>
</RadioGroup>
<CheckboxGroup
className="col-span-2"
defaultValue={["1"]}
variant={variant}
>
<Checkbox value="1">Option 1</Checkbox>
<Checkbox value="2">Option 2</Checkbox>
</CheckboxGroup>
</Well>
));Button variants
Buttons support soft, ghost, and solid variants for different visual
emphasis levels. Note that ToggleButton uses the same visual appearance for
soft and solid variants to avoid confusion with the selected state.
import { PushPinIcon } from "@phosphor-icons/react/dist/ssr";
import { Badge } from "@/shim-ui/badge";
import { Button } from "@/shim-ui/button";
import { ToggleButton } from "@/shim-ui/toggle-button";
import { Well } from "@/shim-ui/well";
export default () => (
<>
{(["soft", "solid", "ghost"] as const).map((variant) => (
<Well className="grid grid-cols-[1fr_auto]" key={variant}>
<Badge className="col-span-full">{variant}</Badge>
<Button intent="accent" variant={variant}>
Let's go
</Button>
<ToggleButton aria-label="Pin" intent="accent" variant={variant}>
<PushPinIcon size={16} />
</ToggleButton>
</Well>
))}
</>
);Label position
Form field components support positioning the label on the side or side-end
of the input for more compact layouts.
"use client";
import { Badge } from "@/shim-ui/badge";
import { Select, SelectItem } from "@/shim-ui/select";
import { TextField } from "@/shim-ui/text-field";
import { Well } from "@/shim-ui/well";
export default () =>
(["top", "side", "side-end"] as const).map((labelPosition) => (
<Well className="gap-4" key={labelPosition}>
<Badge>{labelPosition}</Badge>
<TextField
label="Name"
labelPosition={labelPosition}
placeholder="Your name"
/>
<TextField
label="Email"
labelPosition={labelPosition}
placeholder="Your email"
type="email"
/>
<Select
items={[
{ id: "developer", name: "Developer" },
{ id: "designer", name: "Designer" },
{ id: "manager", name: "Manager" },
]}
label="Role"
labelPosition={labelPosition}
placeholder="Select your role"
>
{({ name }) => <SelectItem>{name}</SelectItem>}
</Select>
</Well>
));Theme provider
Use the <Theme> provider to set default theming props for all components in a
subtree. The provider merges values from the nearest ancestor, allowing layered
overrides as needed.
import type { ReactNode } from "react";
import { Theme } from "@/shim-ui/lib/theme";
import "./globals.css";
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<Theme
size={2}
labelPosition="side"
variants={{ field: "soft", button: "ghost" }}
>
{children}
</Theme>
</body>
</html>
);
}Inside a page or component you can scope a more specific override while inheriting the rest of the defaults:
import { Theme } from "@/shim-ui/lib/theme";
import { Button } from "@/shim-ui/button";
export function Callout() {
return (
<Theme variants={{ button: "solid" }}>
{...}
<Button>Get started</Button>
<Button>Learn more</Button>
</Theme>
);
}Prop | Type | Default |
|---|---|---|
size | 1 | 2 | 3 | 4 | 1 |
labelPosition | "top" | "side" | "side-end" | "top" |
variants.button | "soft" | "solid" | "ghost" | "soft" |
variants.field | "classic" | "soft" | "outline" | "classic" |
variants.table | "surface" | "ghost" | "zebra" | "classic" |
variants.badge | "surface" | "soft" | "solid" | "surface" |
variants.well | "classic" | "soft" | "surface" | "outline" | "classic" |