Skip to content
Facade UI
Colour mode
GitHub (opens in a new tab)
lib

Section contracts

Shared types every Facade section speaks: heading levels, pluggable image/link components, CTA items.

Installation

Package manager
pnpm dlx shadcn@latest add https://facadeui.dev/r/types.json

Source

What the CLI copies into your project, byte for byte.

lib/types.ts
/**
 * Shared contracts for every Facade UI section.
 *
 * Framework neutrality: nothing here imports from `next/*`. Sections that render
 * images or links inside data-driven lists take a *component type* rather than a
 * render callback, so `next/image` and `next/link` can be handed over directly
 * and still work inside React Server Components:
 *
 *   import Image from "next/image"
 *   <LogoCloud items={logos} image={Image} />
 *
 * One-off media uses a `media` ReactNode slot instead — no indirection needed.
 *
 * a11y: `HeadingLevel` exists so a section never hard-codes `<h2>`. Whoever
 * places the section knows the surrounding outline; the section does not.
 *
 * Dependencies: react (types only).
 */

import type { ElementType, ReactNode } from "react"

/** `<h1>` is reserved for the page; sections start at `<h2>`. */
export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6

/** Maps a heading level to its tag. Use the `Heading` atom to render one. */
export type HeadingTag = `h${HeadingLevel}`

/**
 * The subset of image props Facade sections pass through. Intentionally all
 * valid DOM attributes so the default `"img"` element works unchanged, while
 * remaining a structural match for `next/image`.
 */
export interface FacadeImageProps {
  src: string
  /** Required. Pass `""` only for images that are genuinely decorative. */
  alt: string
  width?: number
  height?: number
  className?: string
  sizes?: string
  loading?: "eager" | "lazy"
  decoding?: "async" | "auto" | "sync"
  fetchPriority?: "high" | "low" | "auto"
}

/** Drop-in for `next/image`, or the default `"img"`. */
export type ImageComponent = ElementType<FacadeImageProps>

/** The subset of anchor props Facade sections pass through. */
export interface FacadeLinkProps {
  href: string
  children?: ReactNode
  className?: string
  target?: string
  rel?: string
  "aria-current"?: "page" | "step" | "location" | "date" | "time" | "true" | "false"
  "aria-label"?: string
}

/** Drop-in for `next/link`, or the default `"a"`. */
export type LinkComponent = ElementType<FacadeLinkProps>

/**
 * Any icon component. Structural rather than nominal, so `lucide-react`,
 * `@heroicons/react`, a local SVG component, or anything else that accepts
 * SVG props can be passed without the registry depending on that library.
 *
 * One React Server Components caveat, which bites in exactly one place: an icon
 * component cannot be passed as a prop *across* a server-to-client boundary.
 * Most icon libraries, `lucide-react` included, do not mark their modules
 * `"use client"`, so the reference is a plain function that React refuses to
 * serialise. Static sections are server components and render icons in the same
 * tree, so they are unaffected. A `-motion` variant *is* a client component, so
 * whatever renders it must be a client component too. Adding `"use client"` to
 * the file that passes the icons is the whole fix.
 */
export type IconComponent = ElementType<{
  className?: string
  "aria-hidden"?: boolean | "true" | "false"
  strokeWidth?: number | string
  focusable?: boolean | "true" | "false"
}>

/** A call to action rendered by `CtaGroup` and by most sections' `actions` slot. */
export interface CtaItem {
  label: string
  href: string
  /** Defaults to `primary` for the first action and `outline` for the rest. */
  variant?: "primary" | "secondary" | "outline" | "ghost"
  /** Set for links that leave the site; adds `rel="noopener noreferrer"`. */
  external?: boolean
  "aria-label"?: string
}

/**
 * How a section's list is built, so the `-motion` variant can be a real wrapper.
 *
 * The static section renders `<List>` and `<Item>` from these slots, defaulting
 * to plain `ul`/`li`. The motion variant passes Base-UI-free `Stagger` and
 * `StaggerItem` adapters instead, which is what lets one file stay entirely free
 * of motion imports while the other gets per-item choreography — rather than
 * settling for a single fade over the whole band.
 */
export interface ListSlotProps {
  /** Wraps the list. Defaults to `"ul"`. */
  listAs?: ElementType
  /** Wraps each item. Defaults to `"li"`. */
  itemAs?: ElementType
}

/**
 * The stack equivalent of `ListSlotProps`, for sections whose content is a
 * sequence of blocks rather than a list — heroes and CTA bands.
 *
 * Defaults to plain `div`s, so the static section is a single flex column with
 * no wrappers of consequence. The `-motion` variant passes `Stagger` and
 * `StaggerItem`, which is how the eyebrow, headline, copy and buttons arrive in
 * sequence without the static file importing anything from `motion`.
 */
export interface StackSlotProps {
  /** Wraps the content stack. Defaults to `"div"`. */
  stackAs?: ElementType
  /** Wraps each block in the stack. Defaults to `"div"`. */
  blockAs?: ElementType
}

/** Props shared by every section: outline control plus a styling hook. */
export interface SectionBaseProps {
  /** Heading level for the section's own title. Defaults to `2`. */
  headingLevel?: HeadingLevel
  /** Root element. Defaults to `"section"`. Use `"div"` when already inside one. */
  as?: "section" | "div" | "article" | "aside"
  className?: string
  /** Vertical rhythm. Maps to the `--facade-section-y*` tokens. */
  spacing?: "sm" | "md" | "lg" | "none"
  id?: string
}

Props

FacadeImageProps

The subset of image props Facade sections pass through. Intentionally all valid DOM attributes so the default `"img"` element works unchanged, while remaining a structural match for `next/image`.

Props for FacadeImageProps
PropTypeDefault
src*Requiredstring
alt*RequiredRequired. Pass `""` only for images that are genuinely decorative.string
widthnumber
heightnumber
classNamestring
sizesstring
loading"eager" | "lazy"
decoding"async" | "auto" | "sync"
fetchPriority"high" | "low" | "auto"

FacadeLinkProps

The subset of anchor props Facade sections pass through.

Props for FacadeLinkProps
PropTypeDefault
href*Requiredstring
childrenReactNode
classNamestring
targetstring
relstring
aria-current"page" | "step" | "location" | "date" | "time" | "true" | "false"
aria-labelstring

ListSlotProps

How a section's list is built, so the `-motion` variant can be a real wrapper. The static section renders `<List>` and `<Item>` from these slots, defaulting to plain `ul`/`li`. The motion variant passes Base-UI-free `Stagger` and `StaggerItem` adapters instead, which is what lets one file stay entirely free of motion imports while the other gets per-item choreography — rather than settling for a single fade over the whole band.

Props for ListSlotProps
PropTypeDefault
listAsWraps the list. Defaults to `"ul"`.ElementType
itemAsWraps each item. Defaults to `"li"`.ElementType

StackSlotProps

The stack equivalent of `ListSlotProps`, for sections whose content is a sequence of blocks rather than a list — heroes and CTA bands. Defaults to plain `div`s, so the static section is a single flex column with no wrappers of consequence. The `-motion` variant passes `Stagger` and `StaggerItem`, which is how the eyebrow, headline, copy and buttons arrive in sequence without the static file importing anything from `motion`.

Props for StackSlotProps
PropTypeDefault
stackAsWraps the content stack. Defaults to `"div"`.ElementType
blockAsWraps each block in the stack. Defaults to `"div"`.ElementType

SectionBaseProps

Props shared by every section: outline control plus a styling hook.

Props for SectionBaseProps
PropTypeDefault
headingLevelHeading level for the section's own title. Defaults to `2`.HeadingLevel
asRoot element. Defaults to `"section"`. Use `"div"` when already inside one."section" | "div" | "article" | "aside"
classNamestring
spacingVertical rhythm. Maps to the `--facade-section-y*` tokens."sm" | "md" | "lg" | "none"
idstring