Theming
Shim ships with a theme system that keeps tokens, variants, and layout patterns in sync across all components. The examples below reuse the Field component playground to demonstrate how the same controls adapt across different surfaces.
Size
All components support four sizes that scale proportionally across components.
size: 1
size: 2
size: 3
size: 4
import { ListIcon } from "@phosphor-icons/react/dist/ssr";
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 { 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";
export default () => (
<>
{/* biome-ignore lint/style/noMagicNumbers: no magic */}
{([1, 2, 3, 4] as const).map((size) => (
<div className="flex flex-col gap-3" key={size}>
<code className="block font-medium">size: {size}</code>
<div className="flex gap-2">
<Button className="flex-1" size={size}>
Button
</Button>
<MenuTrigger>
<Button aria-label="Menu" size={size}>
<ListIcon size={ICON_SIZE_MAP[size]} />
</Button>
<Menu size={size}>
<MenuItem>Edit</MenuItem>
<MenuItem>Duplicate</MenuItem>
<MenuSeparator />
<MenuItem intent="danger">Delete</MenuItem>
</Menu>
</MenuTrigger>
</div>
<ToggleButtonGroup size={size}>
<ToggleButton id="1">Left</ToggleButton>
<ToggleButton id="3">Right</ToggleButton>
</ToggleButtonGroup>
<TextField aria-label="TextField" defaultValue="Hello" size={size} />
<div className="flex gap-3">
<Slider aria-label="Slider" className="flex-1" isFilled size={size} />
<Switch aria-label="Switch" size={size} />
</div>
</div>
))}
</>
);
Variants
Field variants
Forms and field-based components support classic
, soft
and outline
variants that adjust visual styles while preserving spacing and layout.
Classic
Soft
Outline
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";
export default () => (
<>
<div>
<h5 className="text-neutral-text!">Classic</h5>
<div className="grid grid-cols-4 gap-4">
<TextField className="col-span-2" defaultValue="Hello" />
<NumberField className="col-span-2" defaultValue={0} />
<SearchField className="col-span-full" />
<Switch className="col-span-1" defaultSelected />
<Slider className="col-span-3" defaultValue={50} isFilled />
<RadioGroup className="col-span-2" defaultValue="1">
<Radio value="1">Option 1</Radio>
<Radio value="2">Option 2</Radio>
</RadioGroup>
<CheckboxGroup className="col-span-2" defaultValue={["1"]}>
<Checkbox value="1">Option 1</Checkbox>
<Checkbox value="2">Option 2</Checkbox>
</CheckboxGroup>
</div>
</div>
<div>
<h5 className="text-neutral-text!">Soft</h5>
<div className="grid grid-cols-4 gap-4">
<TextField className="col-span-2" defaultValue="Hello" variant="soft" />
<NumberField className="col-span-2" defaultValue={0} variant="soft" />
<SearchField className="col-span-full" variant="soft" />
<Switch className="col-span-1" defaultSelected variant="soft" />
<Slider
className="col-span-3"
defaultValue={50}
isFilled
variant="soft"
/>
<RadioGroup className="col-span-2" defaultValue="1" variant="soft">
<Radio value="1">Option 1</Radio>
<Radio value="2">Option 2</Radio>
</RadioGroup>
<CheckboxGroup
className="col-span-2"
defaultValue={["1"]}
variant="soft"
>
<Checkbox value="1">Option 1</Checkbox>
<Checkbox value="2">Option 2</Checkbox>
</CheckboxGroup>
</div>
</div>
<div>
<h5 className="text-neutral-text!">Outline</h5>
<div className="grid grid-cols-4 gap-4">
<TextField
className="col-span-2"
defaultValue="Hello"
variant="outline"
/>
<NumberField
className="col-span-2"
defaultValue={0}
variant="outline"
/>
<SearchField className="col-span-full" variant="outline" />
<Switch className="col-span-1" defaultSelected variant="outline" />
<Slider
className="col-span-3"
defaultValue={50}
isFilled
variant="outline"
/>
<RadioGroup className="col-span-2" defaultValue="1" variant="outline">
<Radio value="1">Option 1</Radio>
<Radio value="2">Option 2</Radio>
</RadioGroup>
<CheckboxGroup
className="col-span-2"
defaultValue={["1"]}
variant="outline"
>
<Checkbox value="1">Option 1</Checkbox>
<Checkbox value="2">Option 2</Checkbox>
</CheckboxGroup>
</div>
</div>
</>
);
Button variants
Buttons support soft
, solid
, and ghost
variants.
Soft
Solid
Ghost
import { PushPinIcon } from "@phosphor-icons/react/dist/ssr";
import { Button } from "@/shim-ui/button";
import { Theme } from "@/shim-ui/lib/theme";
import { ToggleButton } from "@/shim-ui/toggle-button";
import { ToggleButtonGroup } from "@/shim-ui/toggle-button-group";
export default () => (
<>
<div>
<h5 className="text-neutral-text!">Soft</h5>
<div className="grid grid-cols-[1fr_auto] gap-4">
<Button>Hello</Button>
<ToggleButton>
<PushPinIcon size={16} />
</ToggleButton>
<ToggleButtonGroup
className="col-span-full"
defaultSelectedKeys={[1]}
selectionMode="single"
>
<ToggleButton id={1}>
<PushPinIcon size={16} />
</ToggleButton>
<ToggleButton id={2}>
<PushPinIcon size={16} />
</ToggleButton>
<ToggleButton id={3}>
<PushPinIcon size={16} />
</ToggleButton>
</ToggleButtonGroup>
</div>
</div>
<div>
<h5 className="text-neutral-text!">Solid</h5>
<Theme buttonVariant="solid">
<div className="grid grid-cols-[1fr_auto] gap-4">
<Button>Hello</Button>
<ToggleButton>
<PushPinIcon size={16} />
</ToggleButton>
<ToggleButtonGroup
className="col-span-full"
defaultSelectedKeys={[1]}
selectionMode="single"
>
<ToggleButton id={1}>
<PushPinIcon size={16} />
</ToggleButton>
<ToggleButton id={2}>
<PushPinIcon size={16} />
</ToggleButton>
<ToggleButton id={3}>
<PushPinIcon size={16} />
</ToggleButton>
</ToggleButtonGroup>
</div>
</Theme>
</div>
<div>
<h5 className="text-neutral-text!">Ghost</h5>
<Theme buttonVariant="ghost">
<div className="grid grid-cols-[1fr_auto] gap-4">
<Button>Hello</Button>
<ToggleButton>
<PushPinIcon size={16} />
</ToggleButton>
<ToggleButtonGroup
className="col-span-full"
defaultSelectedKeys={[1]}
selectionMode="single"
>
<ToggleButton id={1}>
<PushPinIcon size={16} />
</ToggleButton>
<ToggleButton id={2}>
<PushPinIcon size={16} />
</ToggleButton>
<ToggleButton id={3}>
<PushPinIcon size={16} />
</ToggleButton>
</ToggleButtonGroup>
</div>
</Theme>
</div>
</>
);
Set global defaults
Wrap your application (or any subtree) in the <Theme>
provider to broadcast shared props to every Shim component underneath it. The provider merges values from the nearest ancestor, so you can layer 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 fieldVariant="soft" labelPosition="side" size={2}>
{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 buttonVariant="solid">
<Button>Download</Button>
</Theme>
);
}
Theme API reference
Prop | Type | Default | Description |
---|---|---|---|
|
|
| Sets the base size for all components. Sizes scale proportionally across components. |
|
|
| Sets the default position for labels on field-based components. |
|
|