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
Installation
pnpm dlx shadcn@latest add https://facadeui.dev/r/usp-list.jsonPulls in container, feature-icon, heading, section, section-header, types, utils. The CLI installs them for you.
Usage
The exact source of the preview above.
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.
/**
* 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.
| Prop | Type | Default |
|---|---|---|
items*Required | UspItem[] | — |
title | string | — |
eyebrow | string | — |
description | string | — |
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" |