{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "feature-tabs",
  "type": "registry:block",
  "title": "Feature tabs",
  "description": "Features shown in tabs, built on Base UI. A tab opens when you press Enter, not when you move to it with the arrow keys.",
  "categories": [
    "section"
  ],
  "dependencies": [
    "@base-ui-components/react@1.0.0-rc.0"
  ],
  "registryDependencies": [
    "https://facadeui.dev/r/container.json",
    "https://facadeui.dev/r/heading.json",
    "https://facadeui.dev/r/section.json",
    "https://facadeui.dev/r/section-header.json",
    "https://facadeui.dev/r/types.json",
    "https://facadeui.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "src/sections/feature-tabs.tsx",
      "type": "registry:component",
      "target": "components/sections/feature-tabs.tsx",
      "content": "\"use client\"\n\n/**\n * FeatureTabs — features behind a tab strip, built on Base UI Tabs.\n *\n * a11y: Base UI supplies the roving tabindex, arrow-key movement, Home/End, and\n * the `aria-controls`/`aria-labelledby` pairing between each tab and its panel.\n * What this file decides:\n *\n *  - Tabs are *not* activated on focus. Automatic activation means arrowing\n *    through the strip swaps the panel under the user on every keypress, which\n *    is disorienting and, with real content, expensive. `activateOnFocus` is\n *    off, so a tab is selected with Enter or Space.\n *  - Each panel is focusable, because a panel holding more than a line of text\n *    is a region a keyboard user needs to be able to reach and scroll.\n *  - The strip scrolls horizontally on narrow screens rather than wrapping.\n *    A wrapped tab strip breaks the one-row mental model the arrow keys imply.\n *  - Panel headings sit a level below the section's own.\n *\n * Dependencies: @base-ui-components/react, react, @/lib/types,\n * @/lib/utils, @/components/ui/container, @/components/ui/heading,\n * @/components/ui/section, @/components/ui/section-header.\n */\n\nimport { Tabs } from \"@base-ui-components/react/tabs\"\nimport type { ElementType, ReactNode } from \"react\"\n\nimport type { HeadingLevel, IconComponent, SectionBaseProps } from \"@/lib/types\"\nimport { cn, slugId } from \"@/lib/utils\"\nimport { Container } from \"@/components/ui/container\"\nimport { Heading } from \"@/components/ui/heading\"\nimport { Section } from \"@/components/ui/section\"\nimport { SectionHeader } from \"@/components/ui/section-header\"\n\nexport interface FeatureTabItem {\n  /** Short label for the tab itself. */\n  label: string\n  /** Panel heading. Defaults to `label`. */\n  title?: string\n  description: string\n  icon?: IconComponent\n  /** Panel media — a screenshot, a diagram, a video. */\n  media?: ReactNode\n  bullets?: string[]\n  /** Stable value. Defaults to a slug of the label. */\n  value?: string\n}\n\nexport interface FeatureTabsProps extends SectionBaseProps {\n  items: FeatureTabItem[]\n  title?: string\n  eyebrow?: string\n  description?: string\n  /** Which tab starts selected. Defaults to the first. */\n  defaultValue?: string\n  itemHeadingLevel?: HeadingLevel\n  align?: \"start\" | \"center\"\n}\n\nconst valueOf = (item: FeatureTabItem): string => item.value ?? slugId(item.label, \"tab\")\n\nexport function FeatureTabs({\n  items,\n  title,\n  eyebrow,\n  description,\n  defaultValue,\n  itemHeadingLevel,\n  align = \"center\",\n  headingLevel = 2,\n  as,\n  spacing = \"md\",\n  className,\n  id,\n}: FeatureTabsProps) {\n  const titleId = title ? (id ? `${id}-title` : slugId(title)) : undefined\n  const itemLevel = itemHeadingLevel ?? (Math.min(headingLevel + 1, 6) as HeadingLevel)\n  const first = items[0]\n\n  return (\n    <Section\n      as={as ?? (title ? \"section\" : \"div\")}\n      spacing={spacing}\n      labelledBy={titleId}\n      id={id}\n      className={className}\n    >\n      <Container className=\"flex flex-col gap-12\">\n        {title ? (\n          <SectionHeader\n            align={align}\n            eyebrow={eyebrow}\n            title={title}\n            description={description}\n            headingLevel={headingLevel}\n            titleId={titleId}\n          />\n        ) : null}\n\n        <Tabs.Root\n          defaultValue={defaultValue ?? (first ? valueOf(first) : undefined)}\n          className=\"flex flex-col gap-10\"\n        >\n          <Tabs.List\n            // Selection on Enter/Space, not on arrow-key focus. This is Base\n            // UI's default; it is set explicitly because it is a decision, and\n            // the opposite is what most tab strips do.\n            activateOnFocus={false}\n            className={cn(\n              \"border-border -mx-gutter px-gutter relative flex gap-1 overflow-x-auto border-b\",\n              align === \"center\" && \"sm:justify-center\",\n            )}\n          >\n            {items.map((item) => {\n              const Icon = item.icon as ElementType | undefined\n              return (\n                <Tabs.Tab\n                  key={valueOf(item)}\n                  value={valueOf(item)}\n                  className={cn(\n                    \"text-muted-foreground hover:text-foreground data-[selected]:text-foreground\",\n                    \"duration-facade-fast ease-facade-out flex min-h-12 shrink-0 cursor-pointer items-center gap-2\",\n                    \"whitespace-nowrap rounded-t-md px-4 text-sm font-medium transition-colors\",\n                    \"focus-visible:ring-ring outline-none focus-visible:ring-2\",\n                  )}\n                >\n                  {Icon ? (\n                    <Icon aria-hidden focusable=\"false\" className=\"size-4\" />\n                  ) : null}\n                  {item.label}\n                </Tabs.Tab>\n              )\n            })}\n            <Tabs.Indicator className=\"bg-primary duration-facade-base ease-facade-out absolute bottom-0 left-0 h-0.5 w-[var(--active-tab-width)] translate-x-[var(--active-tab-left)] transition-[translate,width]\" />\n          </Tabs.List>\n\n          {items.map((item) => (\n            <Tabs.Panel\n              key={valueOf(item)}\n              value={valueOf(item)}\n              // A panel with real content is a region a keyboard user must reach.\n              tabIndex={0}\n              className=\"focus-visible:ring-ring rounded-lg outline-none focus-visible:ring-2\"\n            >\n              <div className=\"grid items-center gap-10 lg:grid-cols-2 lg:gap-16\">\n                <div className=\"flex flex-col gap-4\">\n                  <Heading\n                    level={itemLevel}\n                    className=\"text-display-sm text-balance font-semibold\"\n                  >\n                    {item.title ?? item.label}\n                  </Heading>\n                  <p className=\"text-muted-foreground text-pretty text-lg\">\n                    {item.description}\n                  </p>\n                  {item.bullets?.length ? (\n                    <ul className=\"text-muted-foreground flex list-disc flex-col gap-2 pl-5 text-sm\">\n                      {item.bullets.map((bullet) => (\n                        <li key={bullet} className=\"text-pretty\">\n                          {bullet}\n                        </li>\n                      ))}\n                    </ul>\n                  ) : null}\n                </div>\n                {item.media ? <div className=\"min-w-0\">{item.media}</div> : null}\n              </div>\n            </Tabs.Panel>\n          ))}\n        </Tabs.Root>\n      </Container>\n    </Section>\n  )\n}\n"
    }
  ],
  "docs": "Import styles/facade-tokens.css in your global stylesheet, after @import \"tailwindcss\". Set headingLevel to fit the page outline (sections default to h2). Pass next/link and next/image through the link and image props. Docs: https://facadeui.dev/components/feature-tabs"
}
