# Features 2: numbered icon grid

> Six features in a hairline grid of numbered cells with an icon, title and one sentence each. Pass your own features array.

A shadcn/ui block from the [@ballmac](https://ui.ballmac.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/ballmac/features-2).

- Page, with a live preview: https://shadcn.exchange/r/ballmac/features-2
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/ballmac/features-2-light-c43715c624b28ac1.png
- Registry item: https://ui.ballmac.com/r/features-2.json
- Categories: blocks, features
- Listed: 2026-10-14, source last changed 2026-10-14

## Install

```bash
npx shadcn@latest add https://ui.ballmac.com/r/features-2.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`
- Registry items: `utils`

## Files

### `registry/ballmac/components/blocks/features-2/features-2.tsx`

```tsx
// Ballmac UI: Features 2. https://ui.ballmac.com/blocks/features-2
import * as React from "react"
import { Accessibility, Boxes, Code2, Moon, Palette, Terminal } from "lucide-react"

import { cn } from "@/lib/utils"

type Feature = { icon: React.ComponentType<{ className?: string }>; title: string; body: string }

type Features2Props = Omit<React.ComponentProps<"section">, "title"> & {
  /** Mono label above the heading. */
  eyebrow?: string
  /** Section heading. */
  title?: string
  /** The features, three per row on desktop. */
  features?: Feature[]
}

const defaults: Feature[] = [
  { icon: Terminal, title: "One-command setup", body: "Install with your package manager of choice. No config files to write." },
  { icon: Code2, title: "Typed end to end", body: "Every API is typed, so your editor catches mistakes before your users do." },
  { icon: Accessibility, title: "Accessible by default", body: "Keyboard support, focus management and ARIA are built in, not bolted on." },
  { icon: Palette, title: "Themeable", body: "Change a few CSS variables and everything follows your brand." },
  { icon: Moon, title: "Dark mode", body: "Designed for both themes from the start, not inverted after the fact." },
  { icon: Boxes, title: "Composable", body: "Small parts that combine, instead of one component with fifty props." },
]

function Features2({ eyebrow = "Why teams switch", title = "Built for the way you already work.", features = defaults, className, ...props }: Features2Props) {
  return (
    <section data-slot="features-2" className={cn("mx-auto max-w-6xl px-4 py-20 sm:px-6 md:py-28", className)} {...props}>
      <div className="max-w-2xl">
        <p className="font-mono text-xs tracking-[0.16em] text-muted-foreground uppercase">{eyebrow}</p>
        <h2 className="mt-4 text-3xl font-semibold tracking-[-0.035em] text-balance sm:text-4xl">{title}</h2>
      </div>
      <ul className="mt-12 grid gap-px overflow-hidden rounded-2xl border bg-border sm:grid-cols-2 lg:grid-cols-3">
        {features.map((f, i) => (
          <li key={f.title} className="bg-background p-7">
            <div className="flex items-center justify-between">
              <f.icon className="size-5" aria-hidden="true" />
              <span className="font-mono text-xs text-muted-foreground tabular-nums">{String(i + 1).padStart(2, "0")}</span>
            </div>
            <h3 className="mt-6 font-semibold tracking-tight">{f.title}</h3>
            <p className="mt-2 text-sm leading-relaxed text-muted-foreground">{f.body}</p>
          </li>
        ))}
      </ul>
    </section>
  )
}

export { Features2, type Features2Props }
```
