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

USP list

Three or four short value propositions, each an icon and a line of copy.

Also ships a motion variant: USP list (motion). The static one is the default.

Preview

Open full width (opens in a new tab)
Preview width

Installation

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

Pulls in container, feature-icon, heading, section, section-header, types, utils. The CLI installs them for you.

Usage

The exact source of the preview above.

demos/usp-list.tsx
import { UspList } from "@registry/sections/usp-list"

import { USPS } from "./content"

export function Demo() {
  return (
    <UspList
      eyebrow="Why"
      title="Three things that matter"
      description="Code quality, accessibility and composability are the product."
      items={USPS}
    />
  )
}

Source

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

components/sections/usp-list.tsx
/**
 * UspList — three or four short value propositions, each an icon and a line.
 *
 * The narrowest of the feature sections on purpose: a USP strip earns its place
 * by being scannable, so the copy is deliberately constrained to a title and a
 * sentence. Reach for `FeatureGrid` when items need more room than that.
 *
 * a11y: a real `<ul>`. Icons are decorative and hidden — the title beside each
 * one carries the meaning — which `FeatureIcon` enforces rather than trusting
 * each caller to remember.
 *
 * Dependencies: react, @/lib/types, @/lib/utils,
 * @/components/ui/container, @/components/ui/feature-icon, @/components/ui/heading,
 * @/components/ui/section, @/components/ui/section-header.
 */

import type { ElementType } from "react"

import type {
  HeadingLevel,
  IconComponent,
  ListSlotProps,
  SectionBaseProps,
} from "@/lib/types"
import { cn, slugId } from "@/lib/utils"
import { Container } from "@/components/ui/container"
import { FeatureIcon } from "@/components/ui/feature-icon"
import { Heading } from "@/components/ui/heading"
import { Section } from "@/components/ui/section"
import { SectionHeader } from "@/components/ui/section-header"

export interface UspItem {
  title: string
  description: string
  icon?: IconComponent
}

export interface UspListProps extends SectionBaseProps, ListSlotProps {
  items: UspItem[]
  title?: string
  eyebrow?: string
  description?: string
  /** Outline level of each item's title. Defaults to one below the section. */
  itemHeadingLevel?: HeadingLevel
  /** `stacked` puts the icon above the copy; `inline` puts it beside. */
  layout?: "inline" | "stacked"
  iconVariant?: "soft" | "solid" | "outline" | "plain"
  align?: "start" | "center"
}

export function UspList({
  items,
  title,
  eyebrow,
  description,
  itemHeadingLevel,
  layout = "inline",
  iconVariant = "soft",
  align = "start",
  listAs,
  itemAs,
  headingLevel = 2,
  as,
  spacing = "md",
  className,
  id,
}: UspListProps) {
  const List = (listAs ?? "ul") as ElementType
  const Item = (itemAs ?? "li") as ElementType
  const titleId = title ? (id ? `${id}-title` : slugId(title)) : undefined
  const itemLevel = itemHeadingLevel ?? (Math.min(headingLevel + 1, 6) as HeadingLevel)

  return (
    <Section
      as={as ?? (title ? "section" : "div")}
      spacing={spacing}
      labelledBy={titleId}
      id={id}
      className={className}
    >
      <Container className="flex flex-col gap-14">
        {title ? (
          <SectionHeader
            align={align}
            eyebrow={eyebrow}
            title={title}
            description={description}
            headingLevel={headingLevel}
            titleId={titleId}
          />
        ) : null}

        <List
          className={cn(
            "grid gap-10 sm:gap-8",
            items.length === 4 ? "sm:grid-cols-2 lg:grid-cols-4" : "sm:grid-cols-3",
          )}
        >
          {items.map((item) => (
            <Item
              key={item.title}
              className={cn(
                "flex gap-4",
                layout === "stacked" ? "flex-col" : "flex-row",
                align === "center" && "items-center text-center",
                align === "center" && layout === "inline" && "flex-col",
              )}
            >
              {item.icon ? <FeatureIcon icon={item.icon} variant={iconVariant} /> : null}
              <div className="flex flex-col gap-1.5">
                <Heading level={itemLevel} className="text-foreground font-semibold">
                  {item.title}
                </Heading>
                <p className="text-muted-foreground text-pretty text-sm">
                  {item.description}
                </p>
              </div>
            </Item>
          ))}
        </List>
      </Container>
    </Section>
  )
}

Props

UspListProps

Extends SectionBaseProps, ListSlotProps.

Props for UspListProps
PropTypeDefault
items*RequiredUspItem[]
titlestring
eyebrowstring
descriptionstring
itemHeadingLevelOutline level of each item's title. Defaults to one below the section.HeadingLevel
layout`stacked` puts the icon above the copy; `inline` puts it beside."inline" | "stacked""inline"
iconVariant"soft" | "solid" | "outline" | "plain""soft"
align"start" | "center""start"