Skip to content

Bento Grid

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.

Motionv0.1.0experimentalWCAG 2.2 AAView spec

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.

  • Components that ship ready

    Each one is built on React Aria, so keyboard and screen reader support is there from the first render.

  • Made for teams

    Shared tokens keep every screen consistent.

  • Readable data

    Tables and charts with real text behind them.

  • Yours to change

    The source is copied into your project, so you can edit it.

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-grid

Copies 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.json

Works, 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>
  • Start here

    Install the CLI, add your first component and see it in your app.

    Read the install guide
  • Theming

    Change the tokens once and every component follows.

    Open theming
  • Accessibility

    What each component promises and how it is tested.

    See the checklist
  • Need a custom flow?

    Press the button to see how an action card behaves.

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

  • Polish by default

    Spacing, focus rings and states are already done.

  • Any language

    Right-to-left layouts work without extra code.

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.

PropTypeDefault
childrenrequired

The BentoCard items.

nodenone
label

Accessible name of the list. Leave it out when a nearby heading already names the section and pass aria-labelledby instead.

stringnone
isStill

Turns off the hover lift and its transition.

booleanfalse

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