{% load brickwork_icons %} {% comment %} Feature grid (04-interfaces.md section 4d): a responsive grid of feature cards. Structural, consumed via {% include %} (BR-BW-MKT ยง4d structural- include doctrine). Required context: none. Optional context: heading (str): the section intro heading. lede (str): the section intro body. items (list of dicts): {icon?, heading, body, url?, aria_label?, eyebrow?, meta?, badge?, cta_label?, pending?}. icon is a registry name rendered via {% bw_icon %} decorative=True (the icon reinforces the heading/body text, which already carries the meaning). An empty (or omitted) items list renders the section intro alone (or nothing at all if that too is empty), never a fabricated placeholder card. List-shaped, so it has no flat-kwarg spelling (#98): a Django template cannot build a list inline, so supply items from context (the view, or a wrapper template's own data); the scalar intro context (heading, lede, columns, variant) is flat and template- authorable as-is. item.url (str, optional, #99): when present the whole card renders as an anchor (bw-feature-card--link) so a feature card can navigate to its subject, taking the marketing kit's link hover affordance (the heading ink moves to accent on hover, with the standard colour transition; the focus ring comes from the global :focus-visible rule). Absent, the card stays the plain non-interactive
, byte-identical to before #99: never a clickable-looking card with no destination (the same contract as _stat.html's href). item.aria_label (str, optional, #99): overrides a linked card's accessible name; omitted, the name is the card's own text content. Ignored without item.url. item.eyebrow (str, optional, #641): a small overline above the heading. Absent means omitted: byte-identical to cards without it. item.meta (str, optional, #641): secondary line under the body (a host, a date, a quiet caption). Absent means omitted. item.badge (str, optional, #641): a short status/flag label near the top of the card. Absent means omitted. item.cta_label (str, optional, #641): visible footer CTA text when the card also has item.url (the whole card remains the link; the label is visible reinforcement, not a second tab stop). Ignored without item.url. item.pending (truthy, optional, #641): reduced-emphasis treatment (bw-feature-card--pending). Without item.url the card stays non-interactive; with item.url it remains a link at reduced emphasis. Absent means omitted. columns (2|3|4, default 3): responsive down to one column on narrow viewports (bw-feature-grid-- selects the CSS grid template). variant ("cards" default | "bordered", #646): grid chrome. Omitted (or the honestly-named "cards") keeps the gapped raised cards and emits no modifier class, byte-identical to before #646. "bordered" is the shared-rule marketing grid: 1px gap over the border colour, a strong outer border, cells on surface with no per-card radius or shadow (bw-feature-grid--bordered). Any other value falls back to the default (same as omitted). States: none: a static grid of cards, no open/closed or hover-only reveal. A linked card (item.url) takes the standard link hover ink shift and the global :focus-visible ring; an unlinked card has no interactive state at all. A pending card is visually de-emphasised; without a url it is not interactive. Accessibility: each card's heading is an

(one level under the section's own

), so a page composing several feature grids keeps a correct outline; the card icon is decorative. A linked card renders as a real wrapping the whole card, never a div with a click handler, and item.aria_label overrides its accessible name when the visible text alone would not describe the destination. Covered by axe.spec.mjs against landing-*.html (its own shipped feature grid) and sections-*.html (features/icon-grid.html), both themes. Responsive: grid-template-columns switches at two named breakpoints: 1 column below --bw-breakpoint-sm (40rem), 2 columns from sm up to below --bw-breakpoint-lg (64rem) regardless of the columns value (2/3/4 all render 2 columns in this band), then the requested column count (2, 3 or 4) from lg upward. Layout: root carries .bw-section; content sits in .bw-section__inner (ADR-057 Phase A / #667). Measure and page gutter come from the section shell, not from `.bw-marketing__content > *` alone. See `_section.html` for the class contract and migration notes. {% endcomment %} {% if heading or lede or items %}
{% endif %}