Browse docs
Browse docs
The surface around content. <Box> collapses MUI's Box + Paper + Card + Joy's Surface into a single component with five visual variants and seven intent colors. Need a card? <Box variant="outlined">. Need a callout? <Box variant="soft" color="warning">. Need a hero banner? <Box variant="solid" color="primary">. Same component, configured.
import { Box, Typography } from '@dashforge/tw';
<Box variant="outlined" rounded="xl" p={6}>
<Typography variant="h3">Workspace settings</Typography>
</Box>{/* Plain — a typed div with padding. The baseline. */}
<Box p={6}>Basic container</Box>
{/* Outlined — light border + subtle bg. The classic "card". */}
<Box variant="outlined" rounded="lg" p={6}>
Card-like surface
</Box>
{/* Elevated — floats with a shadow. */}
<Box variant="elevated" elevation={3} rounded="xl" p={8}>
Floating panel
</Box>Box has one job: be the surface around content. It's not a flex container, not a grid container, not a paragraph. The strict separation between "surface" (Box) and "layout" (Stack, Grid) is intentional — when every <div> in an app gravitates back to Box, the distinction collapses and code becomes harder to read.
| If you need... | Use | NOT |
|---|---|---|
| A container with padding, border, shadow, or background | <Box> | — |
| To arrange children in a row or column with gap | <Stack> | <Box> |
| To arrange children in a 2D grid with columns | <Grid> | <Box> |
| Styled text | <Typography> | <Box> |
| Absolute positioning, overflow control, animation | sx prop on any of the above | shorthand props on Box |
Box's job stops where layout starts. This is why Box has no display, no flex*, no grid* props — and never will.
solid · primary
outlined · neutral
plain · no chrome
import { Box, Typography, Stack } from '@dashforge/tw';
<Stack direction="row" gap={3} wrap justify="center">
<Box variant="solid" color="primary" p={4} rounded="md">
<Typography variant="body2">solid · primary</Typography>
</Box>
<Box variant="outlined" p={4} rounded="md">
<Typography variant="body2">outlined · neutral</Typography>
</Box>
<Box variant="plain" p={4} rounded="md">
<Typography variant="body2" color="muted">plain · no chrome</Typography>
</Box>
</Stack><Box variant="plain" p={4}>plain — just padding and radius</Box>
<Box variant="outlined" color="neutral" rounded="lg" p={4}>outlined neutral</Box>
<Box variant="elevated" elevation={2} rounded="lg" p={4}>elevated, shadow=2</Box>
<Box variant="soft" color="info" rounded="lg" p={4}>soft info — semi-transparent intent</Box>
<Box variant="solid" color="primary" rounded="lg" p={4}>solid primary — filled CTA</Box>| Variant | When to use |
|---|---|
plain (default) | Bare container — padding/radius wrapper inside another card |
outlined | "Card lite" — settings panel, form section, sidebar group |
elevated | Floating UI — modals, dropdowns, hover cards |
soft | Callouts, info boxes, badges — semi-transparent tint |
solid | CTA banners, hero panels, brand surfaces — filled |
{/* outlined per intent */}
<Box variant="outlined" color="primary" p={4}>Primary outlined</Box>
<Box variant="outlined" color="success" p={4}>Success outlined</Box>
<Box variant="outlined" color="warning" p={4}>Warning outlined</Box>
<Box variant="outlined" color="danger" p={4}>Danger outlined</Box>
{/* soft per intent */}
<Box variant="soft" color="primary" p={4}>Primary soft</Box>
<Box variant="soft" color="success" p={4}>Success soft</Box>
<Box variant="soft" color="warning" p={4}>Warning soft</Box>
<Box variant="soft" color="danger" p={4}>Danger soft</Box>
{/* solid per intent */}
<Box variant="solid" color="primary" p={4}>Primary solid</Box>
<Box variant="solid" color="success" p={4}>Success solid</Box>
<Box variant="solid" color="warning" p={4}>Warning solid</Box>
<Box variant="solid" color="danger" p={4}>Danger solid</Box>All 21 surface × intent combos are pre-resolved at the variant level — dark-mode pairs (lighter shade for primary, darker bg for surface) are wired without you thinking about it.
elevation 1
elevation 3
elevation 5
import { Box, Typography, Stack } from '@dashforge/tw';
<Stack direction="row" gap={4} wrap justify="center" align="center">
<Box variant="outlined" elevation={1} p={4} rounded="md">
<Typography variant="body2">elevation 1</Typography>
</Box>
<Box variant="outlined" elevation={3} p={4} rounded="md">
<Typography variant="body2">elevation 3</Typography>
</Box>
<Box variant="outlined" elevation={5} p={4} rounded="md">
<Typography variant="body2">elevation 5</Typography>
</Box>
</Stack><Box variant="elevated" elevation={0} rounded="md" p={4}>elevation 0 — no shadow</Box>
<Box variant="elevated" elevation={1} rounded="md" p={4}>elevation 1 — shadow-sm</Box>
<Box variant="elevated" elevation={2} rounded="md" p={4}>elevation 2 — shadow</Box>
<Box variant="elevated" elevation={3} rounded="md" p={4}>elevation 3 — shadow-md</Box>
<Box variant="elevated" elevation={4} rounded="md" p={4}>elevation 4 — shadow-lg</Box>
<Box variant="elevated" elevation={5} rounded="md" p={4}>elevation 5 — shadow-xl</Box>Elevation lives on a separate axis from variant so a "subtle elevated card" (elevation=1) and a "modal-like elevated card" (elevation=4) share the same variant="elevated".
<Box p={4}>uniform padding 4</Box>
<Box px={6} py={2}>asymmetric — px=6, py=2</Box>
<Box m={2}>uniform margin 2</Box>
<Box mx="0.5">tight horizontal margin</Box>Accepted steps (mirror @dashforge/tw-tokens spacing scale):
0 · '0.5' · 1 · 2 · 3 · 4 · 6 · 8 · 12 · 16 · 24
Note '0.5' is a string because 0.5 would lose precision in object keys.
<Box fullWidth p={4}>Stretches to container width</Box>
<Box fullHeight p={4}>Stretches to container height</Box>For custom dimensions (w-64, h-screen, max-w-3xl), use sx — keeping the shorthand props limited to the two patterns used in ~80% of layouts.
The canonical pattern:
<Box variant="outlined" rounded="xl" p={6}>
<Stack gap={3}>
<Typography variant="h3">Workspace settings</Typography>
<Typography variant="body2" color="muted">
Control access and notifications.
</Typography>
</Stack>
</Box>Box paints the surface. Stack handles the gap. Typography styles the text. Three concerns, three components, zero overlap.
<Box variant="solid" color="primary" rounded="2xl" p={12} fullWidth>
<Typography variant="h1" color="inherit" gutterBottom>
Get started today
</Typography>
<Typography variant="body1" color="inherit">
Two minutes from install to your first component on screen.
</Typography>
</Box>Solid variants set text-white on the container; nested Typography with color="inherit" reads cleanly without per-component prop.
as and asChild{/* `as` — change the tag but keep all Box styles */}
<Box as="section" variant="outlined" p={6}>
Semantic section with card chrome
</Box>
{/* `asChild` — paint Box styles onto an existing element (no extra DOM) */}
<Box variant="elevated" elevation={2} rounded="lg" p={6} asChild>
<article>
<Typography variant="h3">Article-shaped card</Typography>
</article>
</Box>Both as and asChild passed → asChild wins (same rule as Typography).
sxFor utility classes Box deliberately doesn't expose as props (overflow, position, cursor, transitions):
<Box variant="elevated" elevation={2} rounded="lg" p={6}
sx="overflow-hidden cursor-pointer transition-shadow hover:shadow-xl">
Hover me — Box shorthand handles the surface, sx handles the interaction
</Box>tailwind-merge resolves precedence; your sx always wins over the variant defaults when they conflict.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'plain' | 'outlined' | 'elevated' | 'soft' | 'solid' | 'plain' | Surface treatment. |
color | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'neutral' | 'neutral' | Intent. Active for outlined/soft/solid; ignored visually for plain/elevated. |
elevation | 0 | 1 | 2 | 3 | 4 | 5 | 0 | Shadow scale. Most useful with variant="elevated". |
rounded | 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full' | 'none' | Border radius. |
p · px · py | SpacingStep | — | Padding (uniform / horizontal / vertical). |
m · mx · my | SpacingStep | — | Margin (uniform / horizontal / vertical). |
fullWidth | boolean | false | w-full. |
fullHeight | boolean | false | h-full. |
as | ElementType | 'div' | Override the HTML tag. |
asChild | boolean | false | Render via Radix Slot onto the single child. Wins over as. |
sx | string | — | Utility-class override, merged via tailwind-merge (your classes win). |
| ...rest | HTMLAttributes<HTMLDivElement> | — | All native attributes pass through (id, data-*, aria-*, onClick, etc.). |
SpacingStep: 0 \| '0.5' \| 1 \| 2 \| 3 \| 4 \| 6 \| 8 \| 12 \| 16 \| 24 (mirror of the @dashforge/tw-tokens spacing scale).
| Concern | Use instead |
|---|---|
display, flex*, grid*, gap | <Stack> or <Grid> |
position, top, right, bottom, left, z-index | sx |
overflow, cursor | sx |
transition, transform, animation | sx |
Width/height beyond fullWidth/fullHeight | sx (e.g. sx="max-w-3xl w-96") |
import { boxVariants } from '@dashforge/tw';
// boxVariants({ variant: 'outlined', color: 'primary', p: 4 }) → className stringUseful for app-level extensions (e.g. a <StatsCard> that wraps Box with a fixed variant chain).
variant × color compound: 21 visually-distinct combos resolved at the TV level (7 intents × 3 colored variants — outlined/soft/solid). plain and elevated ignore color visually.dark: counterpart baked in (lighter shade for solid bg, darker bg for soft surface). No double-prop dance.elevated is color-agnostic: it always paints a neutral surface (bg-white dark:bg-neutral-900) + shadow. If you need an "elevated + primary tint", use variant="outlined" color="primary" and stack a shadow via sx.<Typography> not <Box>. Box has no text-* defaults.asChild wrapping a Button if you really need a custom shape).