Super-AI-Components

Feature Card Row

"Popular features" / "Start from scratch"

Start from scratch

Popular features — enough cards to need the scroll affordance

Grid of starter templates
Preview of a saved brand kit
Preview of auto-generated captions
Preview of a dubbed clip
Preview of resized aspect ratios

What it is

A horizontally scrollable row of feature cards, used for both "Start from scratch" prompts and "Popular features" shortcuts. Each card is an entity-row's four slots — icon or thumbnail, title, description, trailing — turned on its side and stacked vertically into a card face, so the row and its menu-list cousins never drift apart in behavior.

Why it matters

It's the second thing a user sees on the home surface, right after the composer: a menu of what the product can do before they've typed anything. Descript, Zapier, CapCut and Spline all ship a version of this row, and all four solve the same tension — showing more options than fit on screen without hiding half of them behind a scrollbar nobody notices on a trackpad.

Observed in: Descript · Zapier · CapCut · Spline

Anatomy

  1. 1feature-card-rowRoot — the scrollable region (Carousel), announced to AT via role="region".
  2. 2feature-card-row-itemOne card's slide wrapper; fixed width so cards don't reflow as the row scrolls.
  3. 3feature-card-row-cardThe card surface (shadcn Card) each item renders inside.
  4. 4feature-card-row-thumbnailFull-bleed image at the top of a card, present only in the with-thumbnail state.
  5. 5entity-rowComposed, not rebuilt — the card's icon/title/description/trailing, stacked vertically instead of in a row.
  6. 6feature-card-row-previousThe visible "scroll left" affordance the spec calls out — never optional chrome.
  7. 7feature-card-row-nextThe visible "scroll right" affordance; also what keeps the row reachable by keyboard.

How to use it

Reach for it on a home or empty-state surface when there are more actions or features than a single row can show without scrolling. Give every item in a row the same content shape — all icons or all thumbnails — pick icons for actions the user starts ("Start from scratch") and thumbnails for features that have a visual result worth previewing ("Popular features"). It is one component for both: the difference is entirely in the data you pass, not a variant prop.

Do

  • Wire onSelect on every item so the whole card is the click target, not just a link buried in the description.

  • Keep one content shape per row — all icon cards, or all thumbnail cards — so the row reads as one grid instead of a jumble.

Don't

  • Don't hand-roll a scrolling row with overflow-x-auto and no prev/next buttons — trackpad-only scroll hides half the content and nothing signals more cards exist.

    Script to video

    Turn a written script into a rough cut.

    Clone a voice

    Generate narration from a short sample.

  • Don't mix icon cards and thumbnail cards in the same row — pick one shape so the row doesn't look like two different components stitched together.

    Script to videoTurn a written script into a rough cut.
    Grid of starter templates
    Browse templatesStart from a layout other teams already ship with.

Accessibility

Keyboard
  • One tab stop per card, plus Previous and Next — and only for cards that have `onSelect`. An item without one composes `entity-row`'s non-interactive shape, which is a `div`: not focusable, not activatable, and invisible to everything below.
  • Left and Right scroll the row, but only while focus is already inside it. The handler is a capture listener on the region and the region itself is not focusable, so the arrows work from a card or from Previous/Next and nowhere else. Both keys are `preventDefault`ed, so nothing inside a card can use them for anything else.
  • Previous and Next disable themselves at the ends of the row, which removes them from the tab order — the number of tab stops changes as the user scrolls.
  • There is no way to disable a card. `entity-row` takes a `disabled` prop and this component never passes it, so an unavailable feature has to be left out of `items` rather than dimmed.
Screen reader
  • Every card announces as a toggle button. `entity-row` renders `aria-pressed` whenever it has `onSelect`, so "Start from scratch" arrives as an unpressed toggle rather than as a button that opens something.
  • A card's accessible name is its whole face read as one string — icon text, title, description and anything in `trailing`, concatenated. A card with a two-line description has a two-line name, which is the cost of making the whole card the target.
  • The thumbnail is part of neither that name nor that target. It renders above `entity-row` and outside the button, so its `alt` is announced separately and clicking it does nothing — which is why a decorative-sounding alt costs more here than usual.
  • The row announces as "carousel" through `aria-roledescription`, and each item as "slide", but neither has a name. The root is a `role="region"` with nothing labelling it, so it is a region that is never exposed as a landmark. Pass `aria-label` on `FeatureCardRow` to fix it — the component spreads your props onto that region.
  • Previous and Next are named "Previous slide" and "Next slide" by visually-hidden text, and they are the only thing the row says about position. Nothing reports which card is on screen, or how many there are in total.
  • Scrolling is silent. There is no live region anywhere, so pressing Next tells a listener that nothing changed.
Focus
  • `entity-row` supplies a card's focus ring and the shadcn `Button` supplies Previous and Next's. The region and the slide wrappers have none, and neither ever takes focus.
  • Tab can put focus on a card that is scrolled out of view: the carousel positions with a transform and does not advance to follow focus, so a keyboard user can be working on a card they cannot see.
  • Pressing Next does not move focus to a newly revealed card. Focus stays on the button — right for repeated presses, and it means the cards themselves are only ever reached by Tab.

Watch out for

  • Reaching for a plain overflow-x-auto div instead of the Carousel base — that reimplements the keyboard-reachable region and visible next affordance the spec requires, and axe's scrollable-region-focusable rule already caught this exact class of bug once in this repo.
  • Setting both icon and thumbnail on the same item. Only the thumbnail renders (it sits above the entity-row and there's no icon slot left to fill) — pick one per item instead of relying on that precedence.
  • Shipping a thumbnail with a decorative-sounding alt like "feature preview" — the image is the only visual difference from the icon state, so screen reader users need the alt text to actually describe what the feature produces.

Installation

npx shadcn@latest add https://super-ai-components.vercel.app/r/feature-card-row.json

Requires a shadcn app on Base UI (style base-nova). Radix-based styles such as new-york fail to typecheck: these components use Base UI's render prop and callbacks like onOpenChangeComplete, which Radix primitives do not have.