Service grid (icons)
A grid of services or capabilities, each with an icon.
Installation
npx shadcn@latest add http://localhost:3000/r/services-icon-grid.jsonMKT UI is not published yet, so this points at your local development server. Set NEXT_PUBLIC_REGISTRY_URL to the deployed origin to show the public command.
Also installs: container, section, section-heading, service-card.
Usage
import { Droplets, Gauge, Wrench } from "lucide-react";
import { ServicesIconGrid } from "@/components/services-icon-grid";
<ServicesIconGrid
eyebrow="What we do"
title="Residential and small commercial plumbing"
description="Straightforward pricing on the jobs we are called out for most."
surface="muted"
services={[
{
id: "emergency",
title: "Emergency repairs",
description: "Burst pipes, major leaks, and blocked drains.",
icon: <Droplets />,
price: "From $149 call-out",
inclusions: ["Same-day weekday slots", "Upfront price before work starts"],
},
{
id: "leak-detection",
title: "Leak detection",
description: "Acoustic and thermal detection for slab and wall leaks.",
icon: <Gauge />,
href: "/services/leak-detection",
},
]}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title (required) | ReactNode | — | Section heading. |
| services (required) | ServiceItem[] | — | The services. Each needs a stable `id`, which doubles as the anchor target. |
| eyebrow | ReactNode | — | Short label above the heading. |
| description | ReactNode | — | One or two sentences under the heading. |
| columns | 2 | 3 | 4 | 3 | Columns on wide screens. Two on tablet, always one on mobile. |
| align | "start" | "center" | "start" | Heading alignment. The grid itself is unaffected. |
| action | ReactNode | — | Optional action under the grid, e.g. "See all services". |
| headingAs | "h1" | "h2" | "h3" | "h2" | Heading level. Set it to match the page outline. |
| surface | "default" | "muted" | "brand" | "inverted" | "default" | Background treatment. |
ServiceItem
| Prop | Type | Default | Description |
|---|---|---|---|
| id (required) | string | — | Stable key, also used as the list item's DOM id so you can link to it. |
| title (required) | string | — | What a customer would call this service. |
| description (required) | string | — | One or two sentences on what it covers. |
| icon | ReactNode | — | A Lucide icon element. Rendered decoratively and hidden from screen readers. |
| href | string | — | Turns the whole card into one link to a service detail page. |
| price | string | — | Price or starting price, e.g. "From $149". |
| inclusions | string[] | — | Three or four bullets on what is included. |
Behavior
The grid is a <ul>. That tells a screen-reader user how many services there are
before they start reading them — which is exactly the question someone is asking when
they land on a services section.
A card with href is a single link, not a card containing a "Learn more" link.
That gives a much larger tap target on a phone and produces one tab stop per card
instead of making a keyboard user tab through the list twice.
Cards stretch to equal height within a row, so a service with a longer description does not leave its neighbors floating.
Limitations
- Icon-only. The image and compact linked-card variants (
services-image-grid,services-compact) are not implemented yet. columns={4}gets tight below about 1280px with long service names. Three is the safer default for service businesses.- No built-in filtering or category grouping.
Related
- Composition — where a services section sits in a page