A feature grid is the band a landing page reaches for once the hero has made its promise: the
short list of what the product actually does, each capability with an icon, a name and a sentence,
laid out so a reader can scan it rather than read it. It is where the page stops claiming and
starts showing.

Reach for it when three to six capabilities deserve equal weight. One capability is a paragraph, a
dozen is a table, and anything that needs a price beside it belongs in the pricing band instead.

```tsx demo="marketing/feature-grid-preview"
```

## Usage

```tsx
import { FeatureGrid } from "@asheeui/web";

<FeatureGrid
  title="Everything a campaign needs"
  description="Templates, timing and the numbers that follow."
  items={[
    { title: "Templates", description: "Start from a message that already works." },
    { title: "Scheduling", description: "Send when the reader is awake." },
    { title: "Reporting", description: "See what landed, per segment." },
  ]}
/>
```

`items` is the only required prop, and each feature is a value rather than markup: an optional
`icon`, a `title`, an optional `description`, and an optional `href`. An item may also state an
`id`, which becomes its key when a builder can reorder the list; without one, the position in the
array is used.

Around the features, `eyebrow`, `title` and `description` are the heading block every marketing
band shares, and `children` renders below the grid, which is where a `Button` leading to the full
feature list usually goes. The band is a `Section`, so `spacing` is its vertical room and
`background` is the surface behind the cards.

## The column count

The three counts are the layout family's
[column scale](/concepts/layout-and-spacing): `1`, `2`, `3`, `4`, `6` or `12`. Read them from
narrow to wide, because the breakpoints are min-widths, `columns` is the count at the smallest
viewport, `columnsMd` takes over from `md`, `columnsLg` from `lg`, and a wide screen is answered by
`columnsLg` whatever `columnsMd` states.

Each of the three resolves on its own, and all three have a registered default (one, two, three).
That is the difference from a bare `Grid`: stating `columns={2}` and nothing else gives two columns
at the narrowest width, then two at `md` and three at `lg`, not two everywhere. A band that should
keep one count at every width states all three.

```tsx demo="marketing/feature-grid-columns"
```

The count is also the thing to tune once the copy exists. Four features fill their rows at `2` and
at `4` and leave a gap at `3`; five leave a gap at every count, because nothing in the scale divides
five. Change the count, or the number of features, rather than letting the last row be short by
accident.

## Features that go somewhere

An `href` makes the whole card the destination. The card renders the anchor itself, a native
`<a>`, or the component `link` names, rather than a surface holding a handler, so the pointer,
the keyboard and the announcement agree about what the card is:

- Hover tints the border and adds a shadow across the whole card, and the pointer is a hand over
  all of it; a press scales the surface by a hair.
- The card is the tab stop, so focus draws its ring around the whole card rather than around the
  words inside it.
- A reader hears one link named by everything the card contains, title and description together,
  so the title is the label, and a description written as a second, separate call to action would
  be read as part of it.

A feature with no `href` is the same card without any of that: a `div` with no role, no tab stop
and no press state. It is a description, not a control, so give it a description's copy, and put
the destination on the feature that has one.

`link` is not an action. This band has no `primaryAction` or `secondaryAction`; those belong to the
bands that open and close a page. `link` changes only what the anchor each feature renders is made
of, `component` for a router's own link, `props` for the props it needs, and it applies only to
features that state an `href`, because a card with no destination renders no anchor to replace.

A feature's destination is the item's `href`, and the band passes it to the component as `href` unless
`mapProps` says otherwise. That is the whole adapter: a router that names the same thing `to` is
stated once, for every card, rather than wrapped component by component:

```tsx
import { Link } from "@tanstack/react-router";

// The band hands each feature to `mapProps`; the router names the destination `to`.
<FeatureGrid
  link={{ component: Link, mapProps: (item) => ({ to: item.href }) }}
  items={[
    { title: "Templates", description: "Start from a message that works.", href: "/templates" },
  ]}
/>
```

`mapProps` receives the feature and its position, and what it returns is merged over `link.props`
and replaces the default `href`. A card whose feature states no `href` still renders no anchor, so
`mapProps` is never asked about one.

```tsx demo="marketing/feature-grid-links"
```

## Props

```props FeatureGrid
```

The defaults are a cascade rather than a list. All nine options are registered under
`components.featuregrid`, `columns: 1`, `columnsMd: 2`, `columnsLg: 3`, `gap: "lg"`,
`spacing: "lg"`, `background: "none"`, `align: "center"`, `containerSize: "lg"` and
`contained: true`, so each resolves from the instance, then that configuration section, then the
registered value. State the count once there and every band in the product follows; state it on one
band and only that band changes. `align` moves the heading block alone, and `contained` wraps the
heading and the grid in a `Container` of `containerSize` while the band itself still spans the page.

A page with no configuration gets a centred heading over one column of cards with a `lg` gap, in a
`lg`-spaced band on no background. What the band does not decide is the surface: every feature is a
`bordered` `Card` at `md` density, so its corner radius comes from the application's
`defaultRadius` and the card's own `components.card` settings, and a product that rounds
everything to `full` gets rounded feature cards without this band knowing. `items`, `link`,
`children`, `as` and the heading's content, `eyebrow`, `title`, `description` and the two options
that place it in the outline, `titleAs` and `headingSize`, are instance-only, because they state
this page's features and this page's outline rather than the design's.

## Accessibility

- `title` renders a level-two heading by default and each feature's title a level three, so the
  grid sits under the page's own `<h1>` and every feature is a heading of its own. `titleAs` states
  another level, `h1` for the band whose heading *is* the page's title, `h3` for one nested under
  a section heading, and `headingSize` changes the treatment without touching the level. A band
  with no `title` renders no heading block rather than an empty one.
- The `<section>` the band draws is named by that heading through `aria-labelledby`, so a reader
  navigating by region hears it. Without a `title` there is nothing to point at and the section
  stays unnamed, so pass `aria-label` when a title-less list should still be reachable as a region.
- The features are a list, a `ul` with one `li` per card, so a reader hears how many there are
  and which one they are on before reading it.
- A feature with an `href` is an anchor: announced as a link, named by its own title and
  description, reached with <kbd>Tab</kbd>, and openable in a new tab or copyable as a URL. A
  feature without one has no role and no tab stop, so it is read as text.
- The icon is rendered as given, before the title. Give an inline SVG `aria-hidden`, or a picture
  an empty `alt`, so a feature's name stays its words; anything the glyph itself carries belongs
  in the title.

The heading block, the data-not-markup decision and the one-`h1` rule are shared by every band,
see [Marketing sections](/concepts/marketing-sections).

## Related

```related card, grid, testimonials
```
