Feature grid
Feature columns divided by hairlines, with a plain title and a grey body.
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/feature-grid
Installs in one line
One command copies the source into your project.
Tokens, not hex
Every color is a theme token, so dark mode comes free.
Keyboard first
Focus states and arrow keys work before you style anything.
Three features, no heading.
With a heading
Built for the way you ship
Small parts that compose, with no hidden runtime.
Server components
Static markup by default, client code only where state lives.
Container aware
Layouts respond to their column, not the window.
Typed props
Every slot has a type and a one line comment.
A section heading and lead above the columns.
Six features
Everything a settings page needs
Profile forms
Name, avatar and email with inline validation.
API keys
Create, copy once, revoke. Masked by default.
Billing
Plan, usage and invoices in one table.
Members
Invite by email and set a role per person.
Audit log
Every change with who, what and when.
Danger zone
Delete flows that ask twice and say what goes.
Six features wrap to two rows of three.
Usage
import { FeatureGrid } from "@/components/feature-grid"
<FeatureGrid
title="Built for the way you ship"
features={[
{ title: "Installs in one line", description: "One command copies the source." },
{ title: "Tokens, not hex", description: "Dark mode comes free." },
{ title: "Keyboard first", description: "Focus states from the start." },
]}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | Optional section heading. | |
| description | ReactNode | Optional grey lead under the heading. | |
| features | { title: string; description: string }[] | The features, each a title and a one line description. |
Source
import * as React from "react";
import { cn } from "@/lib/utils";
export type Feature = { title: string; description: string };
type FeatureGridProps = Omit<React.ComponentProps<"section">, "title"> & {
/** Optional section heading above the columns. */
title?: React.ReactNode;
/** Optional grey lead under the heading. */
description?: React.ReactNode;
/** The features. Three reads best; six wraps to two rows. */
features: Feature[];
};
/**
* Feature columns divided by hairlines: a 14px title, a grey body, no icons.
* Three columns from the container's md width, one below. Server component.
*/
export function FeatureGrid({ title, description, features, className, ...props }: FeatureGridProps) {
return (
<section className={cn("@container w-full py-8 @2xl:py-16", className)} {...props}>
<div className="mx-auto w-full max-w-6xl px-4 @2xl:px-6">
{(title || description) && (
<div className="mb-8 flex max-w-2xl flex-col gap-3 @2xl:mb-12">
{title && (
<h2 className="m-0 text-xl leading-tight font-medium tracking-tight text-balance @2xl:text-3xl">
{title}
</h2>
)}
{description && <p className="m-0 text-sm text-muted-foreground @2xl:text-base">{description}</p>}
</div>
)}
<ul className="m-0 grid list-none gap-y-6 p-0 @2xl:grid-cols-3 @2xl:gap-y-10">
{features.map((f, i) => (
<li
key={f.title}
className={cn(
"flex flex-col gap-1.5",
i > 0 && "border-t pt-6 @2xl:border-t-0 @2xl:pt-0",
i % 3 !== 0 && "@2xl:border-l @2xl:pl-6",
i % 3 !== 2 && "@2xl:pr-6",
)}
>
<h3 className="m-0 text-sm font-medium text-foreground/85">{f.title}</h3>
<p className="m-0 text-sm text-muted-foreground">{f.description}</p>
</li>
))}
</ul>
</div>
</section>
);
}
For AI agents
Hand your agent this page, the markdown at /components/feature-grid/prompt.md, or the Copy AI prompt button above.
When to use
- Three or six equal-weight benefits under a hero
- When the copy is short and icons would only decorate
- Any section that should read as quiet text, not cards
When not to use
- Cards with icons or borders, which want the feature card
- Mixed emphasis, which wants the bento
- Long copy or an image per feature, which wants the sticky list
Agent notes
- Three columns from the container's @2xl width, one column with top hairlines below
- Hairlines are border-l on columns 2 and 3 of each row; keep the count a multiple of three
- Titles are 14px foreground/85, bodies muted; there is no icon slot by design
- Server component with no state