Layout
The eyebrow / title / body stack that opens a section. Had been hand-written four times across the template gallery, drifting to three different heading sizes; the only genuine difference between the copies was how the eyebrow was drawn, which is now a variant (badge, rule or plain). Deliberately has no onDark prop — the Surface tone says what colour the text is, so a heading never has to ask what it is sitting on. Visual size and heading level are separate props, so a large heading can still tell the truth about the document outline.
| Prop | Type | Default | Description |
|---|---|---|---|
| actLike | ElementType | — | Alias of `as`. Provided because the requested API uses `actLike="button"` reading. When both `as` and `actLike` are set, `actLike` wins and a dev warning fires. |
| align | enum | — | Text alignment. Defaults to `'start'`. |
| alignContent | AlignContent | — | — |
| alignItems | AlignItems | — | — |
| alignSelf | AlignSelf | — | — |
| alt | string | — | Media `alt`. |
| animateOnView | boolean | ViewportOptions | — | Defer `animation` until the element scrolls into view, instead of running it on mount. Without this, every animated element on a page finishes its entrance before the user has scrolled to it — so a long marketing page animates entirely off-screen and reads as static. Pass `true` for the DS defaults (`once: true`, `amount: 0.25`, and a `-64px` bottom margin so the reveal starts just before the element reaches the fold), or an object to tune them. Ignored when `animation` is absent, when `asChild` is set, and under reduced motion — where the element renders plain and fully visible rather than waiting for a trigger it will never receive. @example <Div animation="riseIn" animateOnView /> <Div animation="fadeIn" animateOnView={{ once: false, amount: 0.5 }} /> |
| animation | enum | — | Animation preset (`fadeIn`, `scaleIn`, `slideInFromBottom`, `slideInFromTop`, `pressScale`). When set, the element renders through `motion.create(...)` with the matching variant. `useReducedMotion()` short-circuits the wrapper so reduced-motion users get a plain element. Skipped when `asChild` is true. |
| animationDelay | number | — | Seconds to wait before the animation starts. Composes with `stagger` on an ancestor. |
| animationDuration | number | "fast" | "normal" | "slow" | "slower" | "deliberate" | — | Animation length — seconds, or a motion token name (`'fast'` · `'normal'` · `'slow'`). Token names resolve through `@apx-ui/tokens`, so a theme-level motion change reaches here. |
| animationEase | string | — | Easing — a token name (`'standard'` · `'emphasized'` · `'decelerate'` · `'accelerate'` · `'linear'`) or any raw CSS easing string. |
| as | ElementType | — | Render element. Defaults to `'div'`. |
| asChild | boolean | — | Radix-style polymorphism — merges Div's props onto the single child element. Mutually exclusive with `as` / `actLike`; `asChild` wins on conflict (dev warning fires). |
| aspectRatio | AspectRatio | — | — |
| backdropFilter | BackdropFilter | — | — |
| background | Background<string | number> | — | — |
| backgroundColor | string | (string & {}) | — | — |
| backgroundImage | BackgroundImage | — | — |
| bg | string | (string & {}) | — | — |
| body | ReactNode | — | One paragraph under the title. Capped to a readable measure. |
| bodyTone | enum | — | Palette role for the supporting paragraph. Defaults to `'muted'`. |
| border | Border<string | number> | — | — |
| borderBottom | BorderBottom<string | number> | — | — |
| borderColor | string | (string & {}) | — | — |
| borderLeft | BorderLeft<string | number> | — | — |
| borderRadius | BorderRadius<string | number> | — | — |
| borderRight | BorderRight<string | number> | — | — |
| borderStyle | BorderStyle | — | — |
| borderTop | BorderTop<string | number> | — | — |
| borderWidth | BorderWidth<string | number> | — | — |
| bottom | Bottom<string | number> | — | — |
| boxShadow | string | (string & {}) | — | — |
| boxSizing | enum | — | — |
| centered | boolean | — | Shortcut for `display: flex; align-items: center; justify-content: center;`. Explicit style props win — if any of the three keys is also supplied, the consumer value is kept. |
| checked | boolean | — | Form control `checked`. |
| className | string | — | Standard className. Appended after the recipe + pseudo classes via `tailwind-merge`. |
| color | string | (string & {}) | — | — |
| columnGap | ColumnGap<string | number> | — | — |
| cursor | Cursor | — | — |
| decorative | boolean | — | Shortcut for the "decorative overlay" pattern — fills the parent (`position: absolute; inset: 0;`), declines pointer events, and adds `aria-hidden="true"` so screen readers skip the purely visual layer. Compose with `gradient` for the soft backdrop pattern, or use it standalone with a custom `backgroundImage` / `className`. |
| disabled | boolean | — | Form control `disabled`. |
| display | Display | — | — |
| displayOn | enum | — | Start hidden, reveal the element at the named breakpoint and above. |
| download | string | boolean | — | Anchor `download`. |
| eyebrow | ReactNode | — | Short label above the title. Omit it for a bare heading. `ReactNode`, not `string`, and for the same reason `title` and `body` are: an eyebrow is often more than one run of text — a number and a label baseline-aligned, a label with an icon. Typing it as a string forced a template with a decorated eyebrow to keep a whole hand-written copy of this component to express one line of it. |
| eyebrowColor | enum | — | Palette role for the `badge` eyebrow. Defaults to `'primary'` — **except** that a `primary` chip on a `primary` ground is violet-on-violet, so pass `'neutral'` inside a `<Section tone="primary">` or `<Surface tone="primary">`. It is a prop rather than something the component detects, because the tone is expressed purely in CSS custom properties: `Surface` re-points what the tokens resolve to, and there is nothing in React for a descendant to read. Detecting it would mean a context that the *ground* has to remember to publish — a second source of truth for a fact the CSS already knows, and one that would be wrong for anyone who set the tone vars by hand. `'neutral'` is the right answer on every brand tone, because [#4](/issues/4)'s tone map points the neutral role at the ground's contrast slot. |
| eyebrowStyle | SectionHeadingEyebrowStyle | — | Fill and shape for the `badge` eyebrow. Ignored by the other two variants. `eyebrowVariant="badge" eyebrowStyle={{ variant: 'solid', shape: 'square' }}` is the filled bar a poster-register template wants. |
| eyebrowVariant | enum | — | How the eyebrow is drawn. Defaults to `'badge'`. |
| fg | string | (string & {}) | — | — |
| filter | Filter | — | — |
| flex | Flex<string | number> | — | — |
| flexBasis | FlexBasis<string | number> | — | — |
| flexDirection | enum | — | — |
| flexGrow | FlexGrow | — | — |
| flexShrink | FlexShrink | — | — |
| flexWrap | enum | — | — |
| fontFamily | FontFamily | — | — |
| fontSize | FontSize<string | number> | — | — |
| fontWeight | FontWeight | — | — |
| gap | Gap<string | number> | — | — |
| gradient | DivGradient | — | Theme-aware background gradient — replaces the verbose `className="bg-[radial-gradient(...)]"` recipe with a single typed prop. - `true` → emit a default radial gradient using the active palette (`primary.subtle` → `transparent`, anchored top-left). Tracks the active theme variant for free. - `DivGradientConfig` object → tweak the gradient (`type`, `from`, `to`, `position`, `size`, `fromStop`, `toStop`). `from` / `to` accept palette token paths (`'success.subtle'`) or any raw CSS color. - `string` → escape hatch — used verbatim as `background-image` (drop in any `radial-gradient(...)` / `linear-gradient(...)` / `url(...)`). Sits in the inline style stack just **above** `sx` and just **below** the curated style props, so explicit `backgroundImage` / `background` overrides still win. Often paired with `decorative` for the "soft backdrop" pattern: <Div decorative gradient /> // theme default <Div decorative gradient={{ position: 'top' }} /> // override one field |
| gridArea | GridArea | — | — |
| gridAutoColumns | GridAutoColumns<string | number> | — | — |
| gridAutoFlow | GridAutoFlow | — | — |
| gridAutoRows | GridAutoRows<string | number> | — | — |
| gridColumn | GridColumn | — | — |
| gridRow | GridRow | — | — |
| gridTemplateColumns | GridTemplateColumns<string | number> | — | — |
| gridTemplateRows | GridTemplateRows<string | number> | — | — |
| h | Height<string | number> | — | — |
| headingId | string | — | `id` for the **heading element**, not the wrapper. On the wrapper it would be nearly useless; on the heading it is what a URL fragment lands on, which is why it comes with `scroll-mt` (see the component). A documentation template keys its sidebar, its on-this-page rail and its scroll-spy off this one id — four things that must agree, so there must only be one of them. |
| height | Height<string | number> | — | — |
| hideOn | enum | — | Hide the element at the named breakpoint and above (Tailwind "from upward" semantics). |
| href | string | — | Anchor `href` — narrow when using `as="a"` / `actLike="a"`. |
| htmlFor | string | — | Label `htmlFor`. |
| inset | Inset<string | number> | — | — |
| intro | SectionIntroLike | — | The whole intro as one object, instead of `eyebrow` / `title` / `body` separately. Templates keep their copy in a content file as exactly this shape, so passing it whole is both shorter at the call site and the thing that makes a CMS-backed section a one-line change. Individual props still win over the corresponding field, so a section can override just the title. |
| justifyContent | JustifyContent | — | — |
| justifyItems | JustifyItems | — | — |
| justifySelf | JustifySelf | — | — |
| left | Left<string | number> | — | — |
| letterSpacing | LetterSpacing<string | number> | — | — |
| level | enum | — | Heading level for the document outline, independent of `size`. Defaults to 2. Separate because a visually-large heading is sometimes an `h3`, and forcing size and level to move together means either the page looks wrong or the outline lies to a screen reader. |
| lineHeight | LineHeight<string | number> | — | — |
| m | Margin<string | number> | — | — |
| margin | Margin<string | number> | — | — |
| marginBlock | MarginBlock<string | number> | — | — |
| marginBottom | MarginBottom<string | number> | — | — |
| marginInline | MarginInline<string | number> | — | — |
| marginLeft | MarginLeft<string | number> | — | — |
| marginRight | MarginRight<string | number> | — | — |
| marginTop | MarginTop<string | number> | — | — |
| maxHeight | MaxHeight<string | number> | — | — |
| maxWidth | MaxWidth<string | number> | — | — |
| mb | MarginBottom<string | number> | — | — |
| minHeight | MinHeight<string | number> | — | — |
| minWidth | MinWidth<string | number> | — | — |
| ml | MarginLeft<string | number> | — | — |
| mr | MarginRight<string | number> | — | — |
| mt | MarginTop<string | number> | — | — |
| mx | MarginInline<string | number> | — | — |
| my | MarginBlock<string | number> | — | — |
| name | string | — | Form field `name`. |
| onActive | string | — | Tailwind classes applied on `:active` (pressed). e.g. `"scale-[0.98]"`. |
| onChecked | string | — | Tailwind classes applied when `aria-checked="true"`. |
| onDataState | string | — | Tailwind classes applied when `data-state="open"` is present. |
| onDisabled | string | — | Tailwind classes applied on `:disabled`. e.g. `"opacity-50 cursor-not-allowed"`. |
| onFocusVisible | string | — | Tailwind classes applied on `:focus-visible` (keyboard focus). e.g. `"ring-2 ring-primary-500"`. |
| onGroupHover | string | — | Tailwind classes applied when a parent `.group` is hovered. |
| onHover | string | — | Tailwind classes applied on `:hover`. e.g. `"bg-primary-100 scale-[1.02]"`. |
| opacity | Opacity | — | — |
| order | Order | — | — |
| overflow | Overflow | — | — |
| overflowX | enum | — | — |
| overflowY | enum | — | — |
| p | Padding<string | number> | — | — |
| padding | Padding<string | number> | — | — |
| paddingBlock | PaddingBlock<string | number> | — | — |
| paddingBottom | PaddingBottom<string | number> | — | — |
| paddingInline | PaddingInline<string | number> | — | — |
| paddingLeft | PaddingLeft<string | number> | — | — |
| paddingRight | PaddingRight<string | number> | — | — |
| paddingTop | PaddingTop<string | number> | — | — |
| pb | PaddingBottom<string | number> | — | — |
| pl | PaddingLeft<string | number> | — | — |
| placeContent | PlaceContent | — | — |
| placeItems | PlaceItems | — | — |
| placeSelf | PlaceSelf | — | — |
| pointerEvents | enum | — | — |
| position | enum | — | — |
| pr | PaddingRight<string | number> | — | — |
| pt | PaddingTop<string | number> | — | — |
| px | PaddingInline<string | number> | — | — |
| py | PaddingBlock<string | number> | — | — |
| radius | BorderRadius<string | number> | — | — |
| ref | Ref<HTMLElement> | — | Ref to the rendered element. Type depends on `as` / `actLike` / `asChild`. Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). @see {@link https://react.dev/learn/referencing-values-with-refs#refs-and-the-dom React Docs} |
| rel | string | — | Anchor `rel`. |
| right | Right<string | number> | — | — |
| rowGap | RowGap<string | number> | — | — |
| shadow | string | (string & {}) | — | — |
| size | enum | — | Visual size. Defaults to `'section'`. |
| src | string | — | Media `src`. |
| stagger | number | — | Cascade descendant `<Div animation="…">` elements instead of firing them together: the number of seconds between each one starting. The children need no extra props — they opt in simply by having an `animation`. Combine with `animateOnView` on this element to make the whole group reveal on scroll as one unit, which is the common case for a card grid or a feature list. @example <Div stagger={0.08} animateOnView> {items.map((i) => <Div key={i.id} animation="riseIn">…</Div>)} </Div> |
| staggerDelay | number | — | Seconds to wait before the first staggered child starts. Requires `stagger`. |
| style | CSSProperties | — | Inline style override. Wins over `sx`, curated style props, and the `centered` shortcut. |
| sx | Sx | — | Theme-aware inline style (resolves palette / spacing / radius tokens). |
| target | string | — | Anchor `target`. |
| textAlign | enum | — | — |
| textDecoration | TextDecoration<string | number> | — | — |
| textOverflow | TextOverflow | — | — |
| textTransform | TextTransform | — | — |
| title | ReactNode | — | The heading itself. Required unless `intro` supplies it. |
| titleMeasure | enum | — | Measure cap on the heading itself. Defaults to `'none'`. A display-scale heading needs its own cap — at 64px an uncapped line runs to a width nobody can track back from. The body has always been capped; the title was not, and three templates added their own. |
| titleWeight | enum | — | Weight of the heading. Defaults to `'bold'`. A prop because it is the single most common reason a template kept its own copy of this component: three of the eight run a *lighter* display weight as their identity, and a bold heading is simply the wrong page for them. Set it once per template in `theme.components.SectionHeading.defaultProps` rather than per section. |
| top | Top<string | number> | — | — |
| transform | Transform | — | — |
| transition | Transition<string & {}> | — | — |
| type | string | — | Button `type` — narrow when using `as="button"` / `actLike="button"`. |
| userSelect | enum | — | — |
| value | string | number | readonly string[] | — | Form field `value`. |
| visibility | enum | — | — |
| w | Width<string | number> | — | — |
| whiteSpace | WhiteSpace | — | — |
| width | Width<string | number> | — | — |
| wordBreak | enum | — | — |
| z | ZIndex | — | — |
| zIndex | ZIndex | — | — |