Shim

Theming

Sizes, variants, and global defaults.

Overview

Shim components support theming options like size and visual variants to adapt the look and feel to your design. Set global defaults using the theme provider to ensure consistency across your application.


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.

Color scales

Each color has a 12-step scale designed for specific use cases. Shim maps these to semantic tokens you can use in your components:

  • Steps 1-2: Backgrounds and subtle surfaces
  • Steps 3-5: Interactive backgrounds (hover, active states)
  • Steps 6-8: Borders and focus rings
  • Steps 9-10: Solid colors and hover states
  • Steps 11-12: Text colors with proper contrast

Semantic color tokens

Shim provides five color intents, each with a full set of use-case tokens:

  • neutral - Gray tones for UI elements
  • accent - Primary brand color (default: Iris)
  • success - Positive states (default: Green)
  • warning - Cautionary states (default: Orange)
  • danger - Destructive actions and errors (default: Red)

Each intent includes tokens like base, panel, bg, bg-hover, bg-active, line, border, border-hover, solid, solid-hover, text, text-contrast, and text-subtle.

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 accent colors

To change the accent color, edit the Radix color imports in your theme.css file. For example, to use blue instead of iris:

styles/theme.css
/* Replace iris imports with blue */
@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 that scale proportionally.

size: 1
size: 2
size: 3
size: 4
Size example
"use client";
import { ListIcon } from "@phosphor-icons/react";
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";
import { Well } from "@/shim-ui/well";

export default () =>
  ([1, 2, 3, 4] as const).map((size) => (
    <Well key={size} size={size}>
      <code className="col-span-full mb-2 text-xs">size: {size}</code>
      <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={["1"]}>
        <ToggleButton id="1">Left</ToggleButton>
        <ToggleButton id="2">Right</ToggleButton>
      </ToggleButtonGroup>
      <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.

variant: "classic"
variant: "soft"
variant: "outline"
Field variants example
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}>
      <code className="col-span-full mb-2 text-xs">variant: "{variant}"</code>

      <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 and ToggleButtonGroup do not support the "solid" variant to avoid confusion with selected states.

variant: "soft"
variant: "ghost"
variant: "solid"
variant: "soft" (ToggleButton)
variant: "ghost" (ToggleButton)
Button variants example
import { PushPinIcon } from "@phosphor-icons/react/dist/ssr";
import { Button } from "@/shim-ui/button";
import { ToggleButton } from "@/shim-ui/toggle-button";
import { ToggleButtonGroup } from "@/shim-ui/toggle-button-group";
import { Well } from "@/shim-ui/well";

export default () => (
  <>
    {(["soft", "ghost", "solid"] as const).map((variant) => (
      <Well className="gap-4" key={variant}>
        <code className="col-span-full mb-2 text-xs">variant: "{variant}"</code>

        <Button className="col-span-full" variant={variant}>
          Button
        </Button>
      </Well>
    ))}

    {(["soft", "ghost"] as const).map((variant) => (
      <Well className="grid grid-cols-[1fr_auto] gap-4" key={variant}>
        <code className="col-span-full mb-2 text-xs">
          variant: "{variant}"{" "}
          <span className="font-normal font-sans">(ToggleButton)</span>
        </code>

        <ToggleButtonGroup defaultSelectedKeys={[1]} variant={variant}>
          <ToggleButton id={1}>Left</ToggleButton>
          <ToggleButton id={2}>Center</ToggleButton>
          <ToggleButton id={3}>Right</ToggleButton>
        </ToggleButtonGroup>
        <ToggleButton 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.

labelPosition: "top"
Role
labelPosition: "side"
Role
labelPosition: "side-end"
Role
Label position example
"use client";
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}>
      <code className="col-span-full mb-2 text-xs">
        labelPosition: "{labelPosition}"
      </code>
      <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>
  ));

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.

layout.tsx
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:

components/callout.tsx
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>
  );
}

Theme API reference

size1 | 2 | 3 | 41
labelPosition"side" | "top""top"
variants{ button?, field?, table?, badge? }{ button: "soft", field: "classic", table: "classic", badge: "surface" }

Variant types

button"soft" | "solid" | "ghost"Button, ToggleButton, ToggleButtonGroup
field"classic" | "soft" | "outline"TextField, Checkbox, RadioGroup, Switch, Slider, and other form inputs
table"classic" | "grid"Table components
badge"surface" | "soft" | "solid"Badge components