Feature card
An icon, a title and a body, alone or in a grid that shares one set of hairlines.
npx shadcn@latest add https://ui.goodtabs.co/r/feature-card.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/feature-card
One calm look
Every component reads your theme tokens, so it matches the rest of the app the moment it lands.
Built for agents
Each page ships a prompt with when to use it, when not to, and the gotchas.
Installs in one line
One shadcn command copies the source into your project. No package to update.
Three features in a FeatureGrid, divided by hairlines, stacked below md.
Single card
Keyboard first
Every control is a real button or link with a visible focus ring, so it works without a mouse.
One FeatureCard carrying its own frame through className.
Four columns
Edge delivery
Served from the region nearest to each visitor.
Private by default
Nothing is public until you share a link.
Instant rollback
Every deploy keeps the last ten versions one click away.
Fair pricing
Pay for what you use, with a free tier that stays free.
columns={4}: two columns from md, four from lg.
Usage
import { FeatureCard, FeatureGrid } from "@/components/ui/feature-card"
import { ZapIcon } from "lucide-react"
<FeatureGrid>
<FeatureCard icon={<ZapIcon />} title="Installs in one line">
One shadcn command copies the source into your project.
</FeatureCard>
</FeatureGrid>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| icon | ReactNode | A 16px icon, drawn in muted-foreground. | |
| title | ReactNode | The feature title, one short line. | |
| children | ReactNode | The body, one or two sentences. | |
| columns | 2 | 3 | 4 | 3 | On FeatureGrid: columns from md up. One column below. |
Source
feature-card/feature-card.tsx
import * as React from "react";
import { cn } from "@/lib/utils";
type FeatureCardProps = Omit<React.ComponentProps<"div">, "title"> & {
/** A 16px icon, drawn in muted-foreground. */
icon?: React.ReactNode;
/** The feature title, one short line. */
title: React.ReactNode;
/** The body, one or two sentences. */
children?: React.ReactNode;
};
/**
* One feature: icon, title and body. Unstyled on the outside so it can sit in a FeatureGrid
* or carry its own frame through className.
*/
export function FeatureCard({ icon, title, className, children, ...props }: FeatureCardProps) {
return (
<div data-slot="feature-card" className={cn("flex flex-col gap-3 bg-card p-6", className)} {...props}>
{icon && (
<span aria-hidden="true" className="inline-flex size-4 shrink-0 text-muted-foreground [&_svg]:size-4">
{icon}
</span>
)}
<div className="flex flex-col gap-1">
<h3 className="m-0 text-sm leading-5 font-medium text-foreground/85">{title}</h3>
{children && <div className="text-sm text-muted-foreground">{children}</div>}
</div>
</div>
);
}
type FeatureGridProps = React.ComponentProps<"div"> & {
/** Columns from md up. One column below. */
columns?: 2 | 3 | 4;
};
const COLUMNS = {
2: "md:grid-cols-2",
3: "md:grid-cols-3",
4: "md:grid-cols-2 lg:grid-cols-4",
} as const;
/**
* A shared-hairline frame for FeatureCard cells: one rounded border, one hairline between every cell.
* The frame paints the hairlines, each cell paints its own card fill.
*/
export function FeatureGrid({ columns = 3, className, ...props }: FeatureGridProps) {
return (
<div
data-slot="feature-grid"
className={cn(
"surface-shine grid w-full gap-px overflow-hidden rounded-lg border bg-border",
COLUMNS[columns],
className,
)}
{...props}
/>
);
}
For AI agents
Hand your agent this page, the markdown at /components/feature-card/prompt.md, or the Copy AI prompt button above.
When to use
- A row of three or four benefits under a hero or a section heading
- Any grid of short, equal-weight points that should read as one surface
- A single highlighted capability inside a longer page
When not to use
- Numbers and trends, which want the stat card
- Tiles of different sizes, which want the bento grid
- Long copy or images per feature, which want their own section
Agent notes
- FeatureCard has no border of its own; inside FeatureGrid the frame draws the hairlines, alone add rounded-lg border through className
- FeatureGrid paints bg-border and each cell paints bg-card, so every cell must stay opaque
- Keep the body to two lines; cells in one row share a height and long copy stretches the whole row
- Server components with no state; the icon is decorative and hidden from screen readers