Components

Surface

Padded, colored panel that stacks its content and remaps text tokens so muted copy stays legible on dark backgrounds.

Install

npx shadcn@latest add @alix/surface

Preview

Default

Muted body text.

Primary

Muted body text.

Sand

Muted body text.

Grey gradient

Muted body text.

Usage

<Surface>
  <p>Default</p>
</Surface>
<Surface bgColor="primary">
  <p>Primary</p>
</Surface>
<Surface bgColor="sand">
  <p>Sand</p>
</Surface>
<Surface bgColor="greyGradient">
  <p>Grey gradient</p>
</Surface>

Examples

01

Secondary service panel

Use secondary when the surface should hold supporting content without becoming a dark feature panel.

Active engagement

Working capital review

Weekly cash actions, supplier terms, and ownership are tracked in one client workspace.

<Surface bgColor="secondary" className="p-6">
  <div className="flex flex-col gap-2">
    <Badge variant="primary">Active engagement</Badge>
    <h3 className="text-sm font-medium text-foreground">Working capital review</h3>
    <p className="text-sm text-muted-foreground text-pretty">
      Weekly cash actions, supplier terms, and ownership are tracked in one client workspace.
    </p>
  </div>
</Surface>
02

Primary decision panel

A primary surface can carry a focused section title and one action when the content needs emphasis.

Prepare the next steering update

Summarise risks, owners, and decisions before the client meeting.
<Surface bgColor="primary">
  <div className="flex flex-col items-start gap-4">
    <SectionTitle
      align="start"
      headingSize="sm"
      title="Prepare the next steering update"
      description="Summarise risks, owners, and decisions before the client meeting."
    />
    <Button variant="secondary">Open workspace</Button>
  </div>
</Surface>
03

Form panel

Surface owns the panel treatment while Field, Input, Select, and Button keep the form contract intact.

<Surface className="max-w-xl p-6">
  <div className="grid gap-4 md:grid-cols-2">
    <Field label="Engagement name">
      <Input defaultValue="Project Atlas" />
    </Field>
    <Field label="Owner">
      <Select
        defaultValue="jp"
        items={[
          { value: "jp", label: "J. Patel" },
          { value: "rs", label: "R. Silva" },
        ]}
      />
    </Field>
  </div>
  <div className="pt-2">
    <Button>Save details</Button>
  </div>
</Surface>

When to use it

Use it for

  • Contained service, insight, or form panels where the background treatment is part of the content hierarchy.
  • Dark primary panels that need token remapping so muted copy stays legible.
  • Small composed regions where the child components should keep their own APIs.

Reach for something else

  • Page-wide section spacing and max-width rules — use SectionWrapper and Container instead.
  • Dashboard cards with built-in headers, actions, or footers — use Panel instead.
  • Gold section surfaces — use Surface with bgColor="primary" or bgColor="secondary" instead.

Props

PropTypeDefaultDescription
bgColor"default" | "primary" | "secondary" | "sand" | "greyGradient" | "goldGradient""default"Full surface treatment — background plus, on dark surfaces, a text and muted-foreground remap that keeps content legible.
childrenReactNodeComposition slot for whatever sits on the surface (a SectionTitle, Text, form, etc.). Surface owns the surface, not its contents.

See also