Feature Bento
Show what your product makes possible. A flexible feature grid that pairs concrete benefits with useful visual previews.
folio®EXAMPLE WORKSPACE
Less scattered. More together.
See the work, at a glance.
Bring projects, notes, and next steps into one calm workspace.
Make room for your team.
Share a project and keep everyone working from the same page.
Know what’s coming next.
Keep reviews and milestones close to the work they belong to.
Less looking. More doing.
Find the document you need, then get back to the good part.
Product benefits, with a preview of how they work.
'use client';
import { useId } from 'react';
import type { ReactNode } from 'react';
import './feature-bento.css';
export interface FeatureBentoItem {
id: string;
title: string;
description: string;
/** Decorative artwork only; keep meaningful information in the title and description. */
media?: ReactNode;
eyebrow?: string;
span?: 'default' | 'wide' | 'tall';
href?: string;
linkLabel?: string;
}
export interface FeatureBentoProps {
items: FeatureBentoItem[];
title?: string;
className?: string;
}
/** A responsive product-benefits grid, with optional links and decorative previews. */
export function FeatureBento({ items, title = 'Small details. A better day.', className = '' }: FeatureBentoProps) {
const headingId = useId();
if (!items.length) return null;
return (
<section className={`ac-feature-bento ${className}`.trim()} aria-labelledby={headingId}>
<header className="ac-feature-bento-header">
<span>MADE FOR YOUR EVERYDAY</span>
<h2 id={headingId}>{title}</h2>
</header>
<div className="ac-feature-bento-grid">
{items.map(item => (
<article className={`ac-feature-bento-card ac-feature-bento-${item.span ?? 'default'}`} key={item.id}>
{item.media && <div className="ac-feature-bento-media" aria-hidden="true" ref={node => { if (node) node.setAttribute('inert', ''); }}>{item.media}</div>}
<div className="ac-feature-bento-copy">
{item.eyebrow && <span className="ac-feature-bento-eyebrow">{item.eyebrow}</span>}
<h3>{item.title}</h3>
<p>{item.description}</p>
{item.href && <a href={item.href} className="ac-feature-bento-link">{item.linkLabel ?? `Explore ${item.title}`}<span aria-hidden="true">↗</span></a>}
</div>
</article>
))}
</div>
</section>
);
}
Installation
Run this in your shadcn-configured React project. The component and stylesheet are added together.
npx shadcn@latest add http://localhost:5173/r/feature-bento.jsonThis command uses the current site address. For localhost, keep this preview running on the same computer. Files follow your configured components alias.
Usage
Import the component and make the details your own.
import { FeatureBento } from '@/components/anticraft/FeatureBento';
export function Example() {
return (
<FeatureBento
title="Less scattered. More together."
items={[
{
id: 'overview', eyebrow: 'ONE SHARED HOME',
title: 'See the work, at a glance.',
description: 'Bring projects, notes, and next steps into one calm workspace.',
span: 'wide',
media: <img src="/images/workspace-overview.jpg" alt="" />,
href: '/features/workspace', linkLabel: 'Explore the workspace',
},
{
id: 'sharing', eyebrow: 'BETTER TOGETHER',
title: 'Make room for your team.',
description: 'Share a project and keep everyone working from the same page.',
media: <img src="/images/team-sharing.jpg" alt="" />,
},
{
id: 'calendar', eyebrow: 'A CLEARER WEEK',
title: 'Know what’s coming next.',
description: 'Keep reviews and milestones close to the work they belong to.',
media: <img src="/images/project-calendar.jpg" alt="" />,
},
{
id: 'search', eyebrow: 'FIND YOUR FLOW',
title: 'Less looking. More doing.',
description: 'Find the document you need, then get back to the good part.',
span: 'wide',
media: <img src="/images/workspace-search.jpg" alt="" />,
href: '/features/search', linkLabel: 'Explore search',
},
]}
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | FeatureBentoItem[] | Required | Features with unique id, title, and description. Optional fields are eyebrow, decorative ReactNode media, span, href, and linkLabel. An empty array renders nothing. DOM reading order always follows your item order. |
items[].media | ReactNode | undefined | Optional decorative product preview or artwork. Media is hidden from assistive technology and rendered inert; keep meaningful information in the title and description, and keep links or controls out of artwork. Supply your own images for the example paths. |
items[].eyebrow | string | undefined | A short visible label above a feature title, such as ONE SHARED HOME. |
items[].span | 'default' | 'wide' | 'tall' | 'default' | Default fills one grid cell, wide spans two columns, and tall spans two rows on the large layout. Tall resets in the two-column layout; all spans reset in the single-column layout. |
items[].href / linkLabel | string | undefined / 'Explore ' + title | An optional real destination and its link text. Omitting href removes the link; the article itself remains a readable feature card rather than a pretend button. |
title | string | 'Small details. A better day.' | The visible section heading and accessible name of the feature section. |
className | string | '' | Additional classes. Theme with --ac-bento-surface, --ac-bento-card, --ac-bento-media, --ac-bento-ink, --ac-bento-muted, --ac-bento-accent, --ac-bento-line, and --ac-bento-font. |
Motion & accessibility
The layout adapts to its own width: three columns by default, two below 740px, and one below 480px. Cards lift slightly on a fine pointer and show a stronger border when a contained link receives focus. Optional feature links are ordinary anchors; cards without links contain no extra keyboard stops. Reduced motion removes the hover lift and arrow movement. The demo’s small workspace views are decorative illustrations, with all essential benefits repeated in the card copy.
Use semantic content and meaningful labels in your composition. Check contrast and keyboard behavior with your own styles and content.