Surface
Surface is the foundational container in cladd. Almost every other component — Button, Toolbar, Input, Switch, Segmented — is composed from it. It carries the depth level, the variant, the accent color, and the optional interactive states (hoverable, clickable, pressed) that the rest of the kit reuses.
This page covers Surface together with its content slot, SurfaceContent.
<Surface
outline
className="w-80 rounded-2xl"
contentClassName="flex flex-col gap-4 p-4"
>
<div className="flex flex-col gap-1">
<div className="font-semibold">Surface</div>
<div className="text-cladd-fg-soft">
Foundational container — carries depth, variant, and accent color.
</div>
</div>
<Surface
outline
className="rounded-xl"
contentClassName="flex items-center justify-between px-4 py-2"
>
<span className="text-cladd-fg-soft">Nested surface</span>
<span className="text-cladd-fg-softer">level +1</span>
</Surface>
</Surface>Usage
import { Surface } from '@cladd-ui/react';
<Surface outline className="w-64 rounded-xl" contentClassName="flex gap-4 p-4">
<span>Hello,</span>
<span>surface</span>
</Surface>;Surface is built from three layers — see Anatomy below for the full picture. The short version: className styles the outer box (size, corner radius, accent color, ring), contentClassName styles the inner content area (padding, flex, grid). Putting flex p-4 on className won't lay out the children, because the children live one wrapper deeper.
Anatomy
A Surface renders up to four layers, in this order:
- Root element — the polymorphic node (
divby default, swappable viaas). CarriesclassName, the depth-level class (cladd-surface-level-N), the accent-color class (cladd-color-{name}), and any forwarded element props. Positionedrelativeso the other layers can sit inside it. - Background layer — an absolutely-positioned
divsized to the root. Paints the variant fill (solid,gradient,*-fill) and the outline ring. Style it viabgClassNameif you need to tweak it. - Overlay layer — an absolutely-positioned sibling of the bg, rendered only when
hoverableorclickableis on. Paints the hover and pressed tints. Where it stacks is controlled byoverlayPosition:'above'(default) places it above the content so the tint covers everything;'below'places it between the bg and the content so the tint only sits on the fill. Style it viaoverlayClassName. - Content wrapper — a
relative h-fulldiv(SurfaceContent) that wrapschildren. Therelativepositioning is the whole point: it lifts the content above the absolute bg and overlay layers so the children render on top. It has no padding,flex, or other layout of its own — that'scontentClassName's job.
This split is why className and contentClassName are separate props. className shapes the box: width, rounded corners, accent token, outline. contentClassName shapes the content layout: padding, flex, grid, gap, alignment. The two never collide.
Bypassing the content wrapper
Pass wrapContent={false} to render children directly instead of wrapping them in SurfaceContent. You rarely need this — the default wrapper is invisible until you put styles on it via contentClassName. Reach for it only when you want multiple stacked content slots (e.g. header / divider / body, each in its own SurfaceContent), or you want full manual control of the inner DOM.
beforeContent
The beforeContent prop renders a sibling slot between the bg layer and the content wrapper — outside the SurfaceContent flow. Use it for focus rings, decorative gradients, or any overlay that needs to sit above the fill but not inside the content layout.
Levels
Every Surface resolves to a depth level from 1 to 5. The level drives the background tone via the cladd-surface-level-N class and propagates to nested surfaces through React context. By default a surface renders one level deeper than its parent, so nesting "just works" without you tracking depth manually.
{[1, 2, 3, 4, 5].map((level) => (
<Surface
key={level}
level={level}
outline
className="rounded-lg"
contentClassName="p-4 text-cladd-fg"
>
Level {level}
</Surface>
))}Nested surfaces auto-bump
A Surface with no level prop reads the current context and renders at parent + 1. Stack them and each child sits one shade above its container, up to the clamp at 5.
<Surface
outline
className="rounded-2xl"
contentClassName="p-4 flex flex-col gap-4 font-mono"
>
<span>Level 1</span>
<Surface
outline
className="rounded-xl"
contentClassName="p-4 flex flex-col gap-4"
>
<span>Level 2</span>
<Surface
outline
className="rounded-lg"
contentClassName="p-4 flex flex-col gap-4"
>
<span>Level 3</span>
<Surface
outline
className="rounded-md"
contentClassName="p-4 flex flex-col gap-4"
>
<span>Level 4</span>
<Surface outline className="rounded-md" contentClassName="p-4">
<span>Level 5</span>
</Surface>
</Surface>
</Surface>
</Surface>
</Surface>Relative offsets
Pass a string like "+1" or "-1" to render relative to the parent context. Useful when a surface needs to break out of the default downward stacking — e.g. an inner panel that should match a sibling rather than its container.
<Surface
level={3}
outline
className="rounded-2xl"
contentClassName="p-4 flex flex-col gap-4 font-mono text-cladd-fg-soft"
>
<span>level={'{3}'}</span>
<div className="flex flex-wrap gap-4">
<Surface
level="-1"
outline
className="rounded-lg"
contentClassName="p-4"
>
level="-1" → 2
</Surface>
<Surface outline className="rounded-lg" contentClassName="p-4">
(default) → 4
</Surface>
<Surface
level="+2"
outline
className="rounded-lg"
contentClassName="p-4"
>
level="+2" → 5
</Surface>
</div>
</Surface>Transparent inherits the parent
A variant="transparent" surface publishes its parent's level (not its own) to descendants. Children render at the same depth as the transparent wrapper — handy for grouping without adding a visual layer.
<Surface
level={2}
outline
className="rounded-2xl"
contentClassName="p-4 flex flex-col gap-4"
>
<span className="text-cladd-fg-soft">Parent (level 2)</span>
<Surface
variant="transparent"
className="rounded-xl border border-dashed border-cladd-outline"
contentClassName="p-4 flex flex-col gap-4"
>
<span>transparent wrapper</span>
<Surface outline className="rounded-lg" contentClassName="p-4">
Child Surface — still level 2
</Surface>
</Surface>
</Surface>Examples
Variants
variant controls the surface's visual treatment. transparent renders no background (children sit at the parent level), solid is the default flat fill, gradient adds a diagonal highlight, and the *-fill variants paint the accent color across the whole surface with inverted text.
<Surface
variant="solid"
outline
className="w-48 rounded-xl"
contentClassName="px-10 py-8 font-medium text-center"
>
solid
</Surface>Colors
Surface accepts any of the eleven cladd accent tokens through color. The token sets cladd-color-{name} on the root, which flows into accent-aware fills, outlines, and inverted text colors on the *-fill variants.
<Surface
color="brand"
variant="gradient-fill"
outline
className="w-48 rounded-xl"
contentClassName="px-10 py-8 font-medium text-center"
>
brand
</Surface>Outline
outline adds a 1px ring around the surface. The ring switches to a fill-aware token automatically when variant ends in -fill, so it stays legible against accent backgrounds.
<Surface
outline
variant="solid"
className="rounded-xl"
contentClassName="px-10 py-8 font-medium"
>
Surface
</Surface>Interactive states
hoverable reveals a hover overlay; clickable adds an active scale + pressed background; pressed forces the pressed state regardless of pointer activity (controlled press). Combine them to turn a surface into a fully interactive target. Use as="button" (or as="a") so the surface itself is the interactive element.
<Surface
as="button"
clickable
hoverable
pressed={false}
variant="solid"
className="rounded-xl"
contentClassName="px-10 py-8 font-medium"
>
Try me
</Surface>Polymorphic root
Surface is polymorphic. Render it as a button, anchor, or any custom component — props of the target element are forwarded automatically.
<Surface
as="a"
href="https://github.com/cladd-ui"
target="_blank"
rel="noreferrer"
clickable
hoverable
outline
className="rounded-xl text-cladd-primary"
contentClassName="px-8 py-4 font-medium"
>
Open the cladd repo →
</Surface>Playground
variant, color, outline, and nesting are designed to compose. The outer surface sets the context — accent color flows down through cladd-color-{name}, and nested surfaces auto-bump one level deeper for visual depth.
<Surface
variant="gradient"
color="brand"
outline
className="w-80 rounded-2xl"
contentClassName="flex flex-col gap-4 p-4"
>
<div className="font-semibold capitalize">
gradient · brand
</div>
<div className="text-cladd-fg-soft">
Each nested surface auto-bumps one level deeper.
</div>
<Surface
variant="gradient"
outline
className="rounded-xl"
contentClassName="flex flex-col gap-4 p-4"
>
<span>level 2</span>
<Surface
variant="gradient"
outline
className="rounded-lg"
contentClassName="flex flex-col gap-4 p-4"
>
<span>level 3</span>
<Surface
variant="gradient"
outline
className="rounded-md"
contentClassName="flex flex-col gap-4 p-4"
>
<span>level 4</span>
<Surface
variant="gradient"
outline
className="rounded-md"
contentClassName="p-4"
>
<span>level 5</span>
</Surface>
</Surface>
</Surface>
</Surface>
</Surface>API Reference
Surface
| Name: Type | Default | Description |
|---|---|---|
| as: ElementType | 'div' | Polymorphic root element. Defaults to 'div'. Use 'button', 'a', etc. when the surface is itself the interactive target (forwarding props of that element). |
| beforeContent: ReactNode | — | Slot rendered between the background layer and the content wrapper, outside the SurfaceContent flex layout (e.g. FocusableLayer, decorative overlays). |
| bgClassName: string | — | Extra classes for the absolutely-positioned background layer (the tinted/outlined fill behind content). |
| children: ReactNode | — | Surface content. |
| className: string | — | Extra classes for the root element. |
| clickable: boolean | — | Enables active/pressed visual states (scale + pressed background). Combine with hoverable. |
| color: Color | — | Accent color token. Sets the surface's cladd-color-{name} class - drives accent-aware borders, fills, and text colors. |
| contentClassName: string | — | Extra classes for the inner SurfaceContent wrapper. Ignored when wrapContent is false. |
| hoverable: boolean | — | Enables hover background overlay. For variant="transparent", also reveals the surface fill on hover. |
| level: number | string | — | Surface depth level (1–5). Drives the background tone via cladd-surface-level="N" classand propagates to nested surfaces through SurfaceContext.Accepts: - An absolute number/string (e.g. 2, "3").- A relative offset against the parent context level (e.g. "+1", "-1").- undefined (default): one level deeper than the parent context.Result is clamped to [1, 5]. For variant="transparent", children inheritcurrentLevel - 1 so they appear at the same depth as this surface. |
| outline: boolean | — | Render a 1px outline ring around the surface. Uses fill-aware token when variant ends in -fill. |
| overlayClassName: string | — | Extra classes for the hover/press overlay layer. |
| overlayPosition: 'below' | 'above' | — | Where to stack the hover/press overlay: - 'below' (default) - inside the background layer, behind content (overlay tints only the bg).- 'above' - on top of content as a separate sibling layer (overlay tints content too). |
| pressed: boolean | — | Force the pressed visual state regardless of pointer activity (controlled press). |
| variant: SurfaceVariant | — | Visual treatment of the surface background: - transparent - no background; children render at the parent level (used for nested groupings).- solid - flat surface fill (default).- gradient - diagonal highlight→surface gradient.- solid-fill - flat primary/accent fill (text inverts to text-cladd-on-primary).- gradient-fill - diagonal accent gradient (text inverts). |
| wrapContent: boolean | — | When true (default), children are rendered inside a SurfaceContent flex wrapper styled by contentClassName.Set to false to render children directly - useful when the surface is the layout root and you want full control of the inner DOM. |
SurfaceContent
| Name: Type | Default | Description |
|---|---|---|
| children: ReactNode | — | Content rendered inside the surface's content layer (above tint/outline, below focus ring). |
| className: string | — | Extra classes for the content wrapper. |