Div
<Div /> is the DS's canonical styling primitive — the equivalent of MUI's Box or
Chakra's Box. A single <div> (or any other element via actLike / as / asChild) with a
flat, token-aware CSS shorthand prop surface plus responsive show/hide, opt-in motion presets,
a centered shortcut, full HTML pass-through, and pseudo-state className hooks.
Reach for <Div> whenever you'd otherwise write <div className="flex items-center p-4 bg-bg-paper">…</div> — it reads better and resolves theme tokens (primary.main, md, …) into
CSS variables for free.
Anatomy
import { Div } from 'apx-ds';
<Div p={4} bg="primary.50" fg="primary.contrast" radius="md">
A token-aware box.
</Div>
<Div display="flex" gap={12} alignItems="center">
<Div flex={1}>Left</Div>
<Div flex={2}>Right</Div>
</Div>
<Div hideOn="md">Mobile-only content</Div>
<Div displayOn="lg">Desktop-only content</Div>
<Div centered h={200}>
<Spinner />
</Div>
<Div actLike="button" type="button" onClick={open} onHover="bg-primary-100" onFocusVisible="ring-2 ring-primary-500">
Click me
</Div>
<Div animation="fadeIn">Fades in on mount</Div>import { Div } from 'apx-ds';
<Div p={4} bg="primary.50" fg="primary.contrast" radius="md">
A token-aware box.
</Div>
<Div display="flex" gap={12} alignItems="center">
<Div flex={1}>Left</Div>
<Div flex={2}>Right</Div>
</Div>
<Div hideOn="md">Mobile-only content</Div>
<Div displayOn="lg">Desktop-only content</Div>
<Div centered h={200}>
<Spinner />
</Div>
<Div actLike="button" type="button" onClick={open} onHover="bg-primary-100" onFocusVisible="ring-2 ring-primary-500">
Click me
</Div>
<Div animation="fadeIn">Fades in on mount</Div>Prop surface
Styling shorthand
Every value flows through the engine's sxToStyle resolver — palette / radius / shadow /
spacing token strings (e.g. bg="primary.main", radius="md") become var(--sds-…),
plain CSS values pass through untouched. Scalars only in v1 (no { base, sm, md } shape
on these props — use hideOn / displayOn / className for responsive layout).
| Group | Props |
|---|---|
| Aliases | m mt mr mb ml mx my p pt pr pb pl px py w h radius shadow z bg fg |
| Layout | display position top right bottom left inset zIndex overflow overflowX overflowY visibility |
| Flex / Grid | flex flexDirection flexWrap flexBasis flexGrow flexShrink alignItems alignContent alignSelf justifyContent justifyItems justifySelf gap rowGap columnGap order + grid* |
| Sizing | width height minWidth maxWidth minHeight maxHeight boxSizing aspectRatio |
| Spacing | margin padding + every directional variant including the logical marginInline / paddingBlock family |
| Color / bg | color backgroundColor background backgroundImage |
| Border | border borderRadius borderWidth borderStyle borderColor borderTop borderRight borderBottom borderLeft |
| Typography | fontSize fontWeight fontFamily lineHeight letterSpacing textAlign textTransform textDecoration whiteSpace textOverflow wordBreak |
| Effects | boxShadow opacity cursor transition transform pointerEvents userSelect filter backdropFilter |
Responsive show / hide
| Prop | Type | Behavior |
|---|---|---|
hideOn | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | Adds ${bp}:hidden — hides from that breakpoint upward. |
displayOn | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | Adds hidden ${bp}:block — start hidden, reveal at that breakpoint. |
Both use Tailwind's mobile-first "from breakpoint upward" semantics, so each emits a single utility class that the JIT scanner picks up at build time.
Animation
| Prop | Type | Behavior |
|---|---|---|
animation | 'fadeIn' | 'scaleIn' | 'slideInFromBottom' | 'slideInFromTop' | Wraps the element via motion.create(...) and applies the matching engine preset. useReducedMotion() short-circuits to a plain element. |
The pressScale preset is not exposed here — it's a whileTap interaction that belongs on
a trigger element (<Button>) instead.
Shortcuts
| Prop | Behavior |
|---|---|
centered | Injects display: flex; align-items: center; justify-content: center; at the lowest style priority, so any explicit value wins. |
decorative | Injects position: absolute; inset: 0; pointer-events: none; and adds aria-hidden="true" so screen readers skip the purely visual layer. Pair with gradient for the soft-backdrop pattern, or use it standalone with a custom backgroundImage / className. |
gradient | Resolves to a background-image value. Accepts true (default theme-aware radial primary.subtle → transparent), a DivGradientConfig (type, from, to, position, size, fromStop, toStop), or a raw CSS string (radial-gradient(...), linear-gradient(...), url(...)). Palette token paths in from / to resolve to var(--sds-palette-…) so the gradient tracks the active theme variant. |
// the verbose className recipe…
<Div className="absolute inset-0 pointer-events-none bg-[radial-gradient(80%_80%_at_0%_50%,var(--sds-palette-primary-subtle)_0%,transparent_70%)]" aria-hidden />
// …becomes a single composed shortcut.
<Div decorative gradient={{ position: 'left' }} />// the verbose className recipe…
<Div className="absolute inset-0 pointer-events-none bg-[radial-gradient(80%_80%_at_0%_50%,var(--sds-palette-primary-subtle)_0%,transparent_70%)]" aria-hidden />
// …becomes a single composed shortcut.
<Div decorative gradient={{ position: 'left' }} />Polymorphism
| Prop | Behavior |
|---|---|
as | Swap the rendered element (<Div as="section">). |
actLike | Alias of as — provided for the requested ergonomic reading <Div actLike="button">. Wins over as when both are set (dev warning). |
asChild | Radix-style — merges Div's props onto the single child element. Mutually exclusive with as / actLike; asChild wins (dev warning). Animation is skipped here. |
Pseudo-state className hooks
Each prop accepts a Tailwind className string. Every whitespace-separated token is automatically
prefixed with the corresponding state variant via the engine's prefixClasses helper.
| Prop | Prefix | Notes |
|---|---|---|
onHover | hover: | Mouse / pen hover. |
onFocusVisible | focus-visible: | Use this for focus rings — keyboard-focus-only is the a11y default. |
onActive | active: | Pressed (mouse-down / Enter). |
onDisabled | disabled: | Disabled form control. |
onChecked | aria-checked: | Triggered by aria-checked="true". |
onGroupHover | group-hover: | Pair with a parent that has the group class. |
onDataState | data-[state=open]: | Radix-style state hook. |
onFocusis intentionally absent because it would collide with React's native focus event handler. UseonFocusVisible(the a11y-correct choice for focus rings) or composefocus:*classes via the standardclassNameprop.
HTML pass-through
id, role, aria-*, data-*, every event handler, plus the common element-specific
attributes (href, type, target, rel, download, src, alt, name, value,
htmlFor, disabled, checked) flow to the rendered element untouched. Use actLike to
swap the element type and the matching attributes "just work":
<Div actLike="a" href="/docs" target="_blank" rel="noreferrer">Open docs</Div>
<Div actLike="button" type="submit" disabled={isSubmitting}>Save</Div><Div actLike="a" href="/docs" target="_blank" rel="noreferrer">Open docs</Div>
<Div actLike="button" type="submit" disabled={isSubmitting}>Save</Div>Style resolution order
Lowest → highest priority:
centereddefaults (display: flex; align-items: center; justify-content: center;)decorativedefaults (position: absolute; inset: 0; pointer-events: none;)gradient(background-image: …)sx(theme-aware object)- Curated style shorthand props (
p={4},bg="primary.50",display="grid", …) - Consumer
styleprop (escape hatch)
Classes layer the same way:
- Recipe (
hideOn/displayOn) theme.components.Div.styleOverrides.root(theme override)- Consumer
className - Pseudo-state hooks (
hover:/focus-visible:/ …)
tailwind-merge handles utility-class conflict resolution (last wins).
Examples
Basic — token-aware padding + bg + radius
Flex — display='flex' + flex={n} on children
Centered — the centered shortcut
HideOn — hide from breakpoint upward
DisplayOn — reveal from breakpoint upward
Animation — opt-in motion presets
Gradient — theme-aware backdrop via the `decorative` + `gradient` shortcuts
ActLike button — real <button> with onClick + pseudo hooks
ActLike anchor — real <a> with href
OnHover — hover-state Tailwind classes
OnFocusVisible — keyboard-only focus ring
TokenStyles — palette tokens across colors
Accessibility
<Div>is a layout primitive — no implicit role, no implicit ARIA, no focus management of its own.- When you swap the element with
actLike(e.g.actLike="button"), the rendered intrinsic element carries its own semantics. Pair withtype="button"and let the native element handle keyboard activation; no extraroleis needed. hideOn/displayOnusedisplay: none, which removes the node from the accessibility tree at the matching breakpoint — that's almost always what you want for purely decorative or duplicate content. For content you want to keep announced but hide visually, prefer a Tailwind utility likesr-onlyviaclassName.
RTL
- No RTL-specific code. The aliases
mx/my/px/pymap to logical properties (marginInline,paddingBlock, …) so they flip automatically underdir="rtl". - Pseudo-state classes (
hover:,focus-visible:, …) are direction-agnostic.
Theming
<Div>reads fromtheme.components.Div.styleOverrides.rootandtheme.components.Div.defaultPropsvia the standarduseThemedClasseshook.- Consumer
classNamealways wins viatailwind-merge.
Bundle
- Static cost: a single
forwardRefcomponent + two small pure helpers (extractStyleProps,buildPseudoClassName) + the existing enginesxToStyle/cv/prefixClasses/motionPresets. - Motion runtime is only pulled in when
animationis set on at least one render — and even thenuseReducedMotion()short-circuits to a plain element.
Do / Don't
- Do reach for
<Div>instead of<div className="…">when you'd otherwise inline more than one or two Tailwind utilities. - Do use
<Div centered>for the centered-spinner / centered-empty-state pattern. - Do prefer
<Div actLike="button">over<Div role="button" tabIndex={0}>— the real element gets you keyboard activation + focus for free. - Don't use
<Div>as a substitute for a real DS component when one exists (<Button>,<Card>,<Stack>,<Field>, …). Those carry richer semantics, recipes, and a11y. - Don't pass responsive objects to the inline-style shorthand props — they expect scalars in
v1. For responsive layout, reach for
hideOn/displayOn/className. - Don't combine
as/actLikewithasChild— pick one. A dev warning fires in development if both are set.