Load More
ProClamps a long region to a couple of rows, fades its cut edge, and puts one reveal button under it. The fade is a mask, not a painted gradient, so it works unchanged on any surface.
Installation#
Usage#
The preview is yours to explore. Its code comes with a Pro license, paid once and used in every project.
Have a license? Unlock it here
Anatomy#
import { LoadMore, LoadMoreContent, LoadMoreTrigger } from "@/components/ui/load-more"
<LoadMore>
<LoadMoreContent>…</LoadMoreContent>
<LoadMoreTrigger more="…" less="…" />
</LoadMore>Collapsed height#
collapsedHeight sets how much shows before the reveal. Pick a value that cuts through a row rather than landing between two: a clean break reads as the end of the list, which is the opposite of the message you want. The default is 28rem.
Alignment#
align moves the reveal button under the region: center (the default, which reads as a page-level affordance), start, or end. The button's row is always in flow, never floated over the fade, so opening costs no layout shift of its own.
Any surface#
The cut edge fades through the fade-b mask utility rather than a background-coloured gradient panel, so it needs to know nothing about what sits behind it. The same component on a solid brand band cuts correctly with no configuration, where a painted gradient would smear the page background across the accent. The band is deep and its ramp is eased, so the clamp trails off instead of ending on a line. With tone="onMedia" the trigger becomes the white chip: on a band it is the only action on the fill, so it takes the light shape.
Without the fade#
fade={false} keeps the region opaque to the last pixel. Reach for it when a soft edge would misread as "this text trails off": a legal notice, a price list, a data table.
Open by default#
defaultExpanded opens on mount, for a deep link that should land on the full list. The component owns the state from there on; pair it with onExpandedChange to react to each toggle.
API reference#
LoadMore
The root that owns the expanded state (uncontrolled). Renders a <div> and forwards all its props. Every part accepts className, merged last.
| Prop | Type | Default | Description |
|---|---|---|---|
| collapsedHeight | string | "28rem" | The clamp, any CSS length. Pick one that cuts through a row, so the fade reads as more below. |
| defaultExpanded | boolean | false | Start revealed. |
| onExpandedChange | (expanded: boolean) => void | - | Called after each toggle. |
| align | "center" | "start" | "end" | "center" | Where the trigger sits under the region. |
LoadMoreContent
The clamped region; put the long grid or list straight inside. Forwards native <div> props.
| Prop | Type | Default | Description |
|---|---|---|---|
| fade | boolean | true | Dissolves the cut edge while collapsed. Skipped when the content already fits. |
| Data attribute | Description |
|---|---|
| data-expanded | Present while the region is revealed. |
| CSS variable | Description |
|---|---|
| --fade-size | Depth of the dissolve, min(16rem, 40%) here. Override per instance, e.g. [--fade-size:6rem]. |
LoadMoreTrigger
The one reveal button, with a caret that turns toward where the region moves. Forwards Button props (children aside) and wires aria-expanded and aria-controls. Renders nothing when the content fits.
| Prop | Type | Default | Description |
|---|---|---|---|
| more* | React.ReactNode | - | Label while collapsed. Name what is below, e.g. "See all components". |
| less* | React.ReactNode | - | Label while expanded, e.g. "Show less". |
| variant | ButtonProps["variant"] | "outline" | Defaults to secondary, the solid chip, under an onMedia or onInverse tone. |