A responsive grid of feature cards of different sizes. It is a real list: each BentoCard is a list item with its own heading, icon, description, optional background and optional link or action. Cards lift a little on hover.
Optional, and kept apart.
Nothing here is included in a project unless you add it, and the plain component it builds on is unchanged. This effect is decoration: it is hidden from screen readers, stands still for visitors who prefer reduced motion, and can be paused. Use Still on a preview to see that version.
import { BentoCard, BentoGrid, ChartIcon, SettingsIcon, SparkleIcon, UsersIcon } from "@rdloom/react";
export default function BentoGridBasicExample() {
return (
<div className="flex w-full justify-center">
<BentoGrid label="Features" className="w-full max-w-4xl">
<BentoCard
colSpan={2}
icon={<SparkleIcon />}
title="Components that ship ready"
description="Each one is built on React Aria, so keyboard and screen reader support is there from the first render."
/>
<BentoCard icon={<UsersIcon />} title="Made for teams" description="Shared tokens keep every screen consistent." />
<BentoCard icon={<ChartIcon />} title="Readable data" description="Tables and charts with real text behind them." />
<BentoCard colSpan={2} icon={<SettingsIcon />} title="Yours to change" description="The source is copied into your project, so you can edit it." />
</BentoGrid>
</div>
);
}Installation
npx rdloom add bento-gridCopies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs react-aria-components; add --install to install them.
It also adds rdloom-motion.css next to your tokens and imports it from the tokens file, once: no setup, and no inline styles. The plain components you already use are untouched.
Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/bento-grid.jsonWorks, but without upgrade tracking, and you import the motion CSS yourself.
Usage
import { BentoCard, BentoGrid, ChartIcon, SettingsIcon, SparkleIcon, UsersIcon } from "@rdloom/react";
<div className="flex w-full justify-center">
<BentoGrid label="Features" className="w-full max-w-4xl">
<BentoCard
colSpan={2}
icon={<SparkleIcon />}
title="Components that ship ready"
description="Each one is built on React Aria, so keyboard and screen reader support is there from the first render."
/>
<BentoCard icon={<UsersIcon />} title="Made for teams" description="Shared tokens keep every screen consistent." />
<BentoCard icon={<ChartIcon />} title="Readable data" description="Tables and charts with real text behind them." />
<BentoCard colSpan={2} icon={<SettingsIcon />} title="Yours to change" description="The source is copied into your project, so you can edit it." />
</BentoGrid>
</div>With links
import { BentoCard, BentoGrid, ChartIcon, SparkleIcon, UsersIcon } from "@rdloom/react";
export default function BentoGridWithLinksExample() {
return (
<div className="flex w-full justify-center">
<BentoGrid label="Guides" className="w-full max-w-4xl">
<BentoCard
rowSpan={2}
icon={<SparkleIcon />}
title="Start here"
description="Install the CLI, add your first component and see it in your app."
href="#install"
actionLabel="Read the install guide"
/>
<BentoCard icon={<UsersIcon />} title="Theming" description="Change the tokens once and every component follows." href="#theming" actionLabel="Open theming" />
<BentoCard icon={<ChartIcon />} title="Accessibility" description="What each component promises and how it is tested." href="#a11y" actionLabel="See the checklist" />
<BentoCard
colSpan={2}
title="Need a custom flow?"
description="Press the button to see how an action card behaves."
onAction={() => undefined}
actionLabel="Show me"
/>
</BentoGrid>
</div>
);
}With background
import { BentoCard, BentoGrid, GlobeIcon, SparkleIcon } from "@rdloom/react";
const glow = (angle: number) => (
<div
className="size-full opacity-40"
style={{ background: `linear-gradient(${angle}deg, color-mix(in srgb, var(--rd-color-action-primary) 30%, transparent), transparent 60%)` }}
/>
);
export default function BentoGridWithBackgroundExample() {
return (
<div className="flex w-full justify-center">
<BentoGrid label="Highlights" className="w-full max-w-4xl">
<BentoCard colSpan={2} background={glow(135)} icon={<SparkleIcon />} title="Polish by default" description="Spacing, focus rings and states are already done." />
<BentoCard background={glow(225)} icon={<GlobeIcon />} title="Any language" description="Right-to-left layouts work without extra code." />
</BentoGrid>
</div>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
childrenrequiredThe BentoCard items. | node | none |
labelAccessible name of the list. Leave it out when a nearby heading already names the section and pass aria-labelledby instead. | string | none |
isStillTurns off the hover lift and its transition. | boolean | false |
Accessibility
Role list, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
- Tab: moves to the link or button of each card in reading order
- Enter: follows the link; Enter or Space presses the button
Screen readers announce
- List with the number of items, then each card: heading, description, then the link or button name
What your code must do
- The grid is a ul and each BentoCard an li, so the number of cards is announced
- Each card title is a real heading; set headingLevel on the cards to fit the page outline
- The icon and the background slot are decoration and are hidden from assistive technology
- A card with a link or action is operated by that one control, whose area covers the whole card; the card never has two tab stops
- The hover lift is off for prefers-reduced-motion, inside rdm-still and with isStill, and only runs where there is a real pointer
- On phones the cards stack in one column; from the medium breakpoint they use three columns, and column and row spans apply there
- Real text in the server HTML; readable with no JavaScript
Guidelines
Use it when
- A feature overview with a few cards that deserve different emphasis
- A landing page section of capabilities
Avoid it when
- Data that must be compared row by row (use a table)
- Long lists of equal items (use a plain list or cards in a regular grid)
Don't
- Putting several links inside one card
- Using spans so large that the grid leaves holes
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-surface-default--rd-color-border-default--rd-color-text-default--rd-color-text-muted--rd-color-focus-ring--rd-radius-overlay