Block
Feature Grid
The features section as a bento rather than a row of identical boxes. A wide before and after leads, a card that turns over sits beside it, and a row of tiles underneath each slide their own detail out from below on hover.
- Image Compare
- Flip Card
- Expanding Card
Everything a real screen needs, already wired
Not a box of parts. Whole sections, with the states nobody budgets for handled before they ship.
Real files land in your project
Fork any of them on day one. They still look like they belong to the same system.
Motion with a reason
Calm curves, durations matched to distance, and a reduced motion path in every file.
Keyboard first
Focus rings, arrow key navigation and Escape handling are in the component, not on your list.
Dark mode from day one
One set of tokens drives both themes, so nothing reads as an afterthought at night.
import { FeatureGrid } from "@/components/blocks/feature-grid";
export default function Page() {
return (
<FeatureGrid
title="Everything a real screen needs, already wired"
compare={{
beforeSrc: "/before.jpg",
beforeAlt: "The screen before",
afterSrc: "/after.jpg",
afterAlt: "The screen after",
}}
highlight={{ front: <h3>Source you own</h3>, back: <p>Not a package.</p> }}
features={[
{
title: "Motion with a reason",
description: "Calm curves and a reduced motion path in every file.",
tone: "violet",
href: "/components",
reveal: <span>See the motion tokens</span>,
},
]}
/>
);
}84 more lines ship with an easeUI Pro license.
Buy a licenseInstall
Props
FeatureGrid
titleRequiredstringThe section heading.
descriptionstringOne line under the heading.
compareFeatureGridCompareThe wide lead tile. Its aspect ratio sets the height of the whole lead row. Left off, the lead row is skipped.
highlight{ front: ReactNode; back: ReactNode }The narrow tile beside the comparison, showing its back face on hover, or on tap where there is no pointer.
featuresRequiredFeatureGridFeature[]The row underneath. Three reads best, and they line up whatever the copy does.
classNamestringMerged last, so anything passed here wins over the component's own classes.
FeatureGridFeature
titleRequiredstringThe tile's heading.
descriptionRequiredstringOne or two lines under it.
revealReactNodeContent for the panel sliding out from under the tile. Text and marks only.
tone"violet" | "neon" | "warning" | "accent"Color of that panel.
hrefstringMakes the tile a link, which is also what gives its reveal a keyboard path.
FeatureGridCompare
beforeSrcRequiredstringThe image on the left of the divider.
beforeAltRequiredstringBoth images are real content, so neither gets an empty alt.
afterSrcRequiredstringThe image on the right of the divider.
afterAltRequiredstringDescribe what changed, not just the subject.
beforeLabelstringDefault "Before"Corner pill on the left.
afterLabelstringDefault "After"Corner pill on the right.
aspectstringDefault "16 / 10"Reserved up front, and what the lead row sizes against.