# Feature Bento

> A features section in three forms: an asymmetric grid of mixed cell sizes, alternating picture and text rows, or a list of features driving one large panel.

A shadcn/ui block from the [@miseui](https://miseui.junsu4621.workers.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/miseui/feature-bento).

- Page, with a live preview: https://shadcn.exchange/r/miseui/feature-bento
- Screenshot: https://shadcn.exchange/shot/miseui/feature-bento/light.png
- Registry item: https://miseui.junsu4621.workers.dev/r/feature-bento.json
- Categories: blocks
- Listed: 2026-11-05, source last changed 2026-11-05

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/miseui/feature-bento.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`, `motion`
- Registry items: `utils`, `https://miseui.junsu4621.workers.dev/r/motion.json`, `https://miseui.junsu4621.workers.dev/r/badge.json`, `https://miseui.junsu4621.workers.dev/r/button.json`, `https://miseui.junsu4621.workers.dev/r/card.json`, `https://miseui.junsu4621.workers.dev/r/gooey-morph.json`, `https://miseui.junsu4621.workers.dev/r/input.json`, `https://miseui.junsu4621.workers.dev/r/materialize.json`, `https://miseui.junsu4621.workers.dev/r/shimmer-text.json`, `https://miseui.junsu4621.workers.dev/r/tabs.json`, `https://miseui.junsu4621.workers.dev/r/thinking-orb.json`

## Files

### `registry/miseui/blocks/feature-bento.tsx`

```tsx
import { FeatureBentoGrid, type BentoFeature, type BentoSpan, type FeatureBentoSectionProps } from "./feature-bento-grid"
import { FeatureBentoRows } from "./feature-bento-rows"
import { FeatureBentoTabs } from "./feature-bento-tabs"

type FeatureBentoVariant = "bento" | "rows" | "tabs"

interface FeatureBentoProps extends FeatureBentoSectionProps {
  /** `bento` cells of mixed size, `rows` alternating picture and text, `tabs` a list driving one large panel. */
  variant?: FeatureBentoVariant
  /** The feature open first on `tabs`. Defaults to the first one. */
  defaultFeatureId?: string
}

function FeatureBento({ variant, defaultFeatureId, ...props }: FeatureBentoProps) {
  switch (variant) {
    case "rows":
      return <FeatureBentoRows {...props} />
    case "tabs":
      return <FeatureBentoTabs defaultFeatureId={defaultFeatureId} {...props} />
    default:
      return <FeatureBentoGrid {...props} />
  }
}

export { FeatureBento, type BentoFeature, type BentoSpan, type FeatureBentoProps, type FeatureBentoVariant }
```

### `registry/miseui/blocks/feature-bento-grid.tsx`

```tsx
import type * as React from "react"
import { cn } from "@/registry/miseui/lib/utils"
import { BeamVisual, MergeVisual, OrbVisual, SendVisual } from "./feature-bento-parts"

type BentoSpan = 2 | 3 | 4 | 6

interface BentoFeature {
  id: string
  /** Written as a sentence: the description runs straight on from it, one tone quieter. */
  title: string
  description: string
  /** A live visual. Without one the cell is all type, set larger so it holds its share of the grid. */
  visual?: React.ReactNode
  /** Columns out of 6, on `bento`. Each row should add up to 6, or the grid leaves a hole. */
  span?: BentoSpan
  /** "dark" flips the theme's tokens inside the cell, on `bento`. One or two per grid set a rhythm; more just reads as a dark page. */
  tone?: "default" | "dark"
}

interface FeatureBentoSectionProps extends Omit<React.ComponentProps<"section">, "title"> {
  title?: string
  /** Continues the title in the same size, one tone quieter. */
  titleQuiet?: string
  /** One cell, row or tab per feature, in order. */
  features?: readonly BentoFeature[]
}

/** Written out so the class names exist for the Tailwind scanner. */
const SPAN_CLASS: Record<BentoSpan, string> = {
  2: "@2xl:col-span-2",
  3: "@2xl:col-span-3",
  4: "@2xl:col-span-4",
  6: "@2xl:col-span-6",
}

const DEFAULT_SPAN: BentoSpan = 3
/** Tinted from the ink, and off in dark mode, where a light-tinted shadow would read as a glow. */
const SOFT_SHADOW_CLASS =
  "shadow-[0_1px_2px_color-mix(in_oklab,var(--foreground)_6%,transparent),0_16px_32px_-16px_color-mix(in_oklab,var(--foreground)_16%,transparent)] dark:shadow-none"

const SAMPLE_FEATURES: readonly BentoFeature[] = [
  {
    id: "after-the-call",
    title: "Keeps working after you hang up.",
    description: "The summary is finished by the time you are back at your desk, with no tab left open.",
    span: 4,
    visual: <BeamVisual />,
  },
  {
    id: "ask",
    title: "Ask the call anything.",
    description: "You get the quote and the minute it was said.",
    span: 2,
    tone: "dark",
    visual: <OrbVisual />,
  },
  {
    id: "no-bot",
    title: "No bot in the call.",
    description: "It records from your own device.",
    span: 2,
  },
  {
    id: "threads",
    title: "Five voices, one page.",
    description: "Everyone's points merge into one record, and split back by speaker when you need to check who said it.",
    span: 4,
    visual: <MergeVisual />,
  },
  {
    id: "send",
    title: "Goes where the team already reads.",
    description: "Email, chat or your tracker, whichever people actually open.",
    span: 6,
    tone: "dark",
    visual: <SendVisual />,
  },
]

function BentoCell({ feature }: { feature: BentoFeature }) {
  const { title, description, visual, span = DEFAULT_SPAN, tone = "default" } = feature
  const dark = tone === "dark"
  const wide = span === 6
  return (
    <li
      data-slot="feature-bento-cell"
      data-span={span}
      className={cn(
        "flex min-w-0 flex-col justify-between gap-6 rounded-2xl border p-6 @4xl:p-8",
        // The dark class flips the tokens on this element, but colour inherits as a value, so it has to be restated.
        dark ? "dark bg-card text-card-foreground" : ["bg-background dark:bg-card text-foreground", SOFT_SHADOW_CLASS],
        wide && "@2xl:flex-row @2xl:items-center",
        SPAN_CLASS[span],
      )}
    >
      {visual ? (
        <div data-testid="feature-bento-visual" className={cn("flex min-w-0 flex-1 items-center justify-center", wide && "@2xl:order-last")}>
          {visual}
        </div>
      ) : null}
      {/* Inline, so one tone-split sentence reads as a single thought while the heading stays a real heading. */}
      <div
        data-testid="feature-bento-copy"
        className={cn("min-w-0 text-pretty", visual ? "text-[15px] leading-relaxed" : "text-display text-2xl @4xl:text-3xl", wide && "@2xl:flex-1")}
      >
        <h3 className={cn("inline", visual && "font-medium")}>{title}</h3> <p className="inline text-muted-foreground">{description}</p>
      </div>
    </li>
  )
}

function SectionTitle({ title, quiet }: { title: string; quiet?: string }) {
  return (
    <h2 className="max-w-2xl text-display text-3xl text-balance @2xl:text-4xl">
      {title} {quiet ? <span className="text-muted-foreground">{quiet}</span> : null}
    </h2>
  )
}

function FeatureBentoGrid({
  title = "After you hang up.",
  titleQuiet = "The notes take care of themselves.",
  features = SAMPLE_FEATURES,
  className,
  ...props
}: FeatureBentoSectionProps) {
  return (
    <section data-slot="feature-bento" data-variant="bento" className={cn("@container w-full bg-muted dark:bg-background", className)} {...props}>
      <div className="mx-auto flex max-w-7xl flex-col gap-8 px-4 py-16 @2xl:gap-12 @2xl:px-8">
        <SectionTitle title={title} quiet={titleQuiet} />
        {features.length > 0 ? (
          <ul className="grid grid-cols-1 gap-4 @2xl:grid-cols-6">
            {features.map((feature) => (
              <BentoCell key={feature.id} feature={feature} />
            ))}
          </ul>
        ) : null}
      </div>
    </section>
  )
}

export { FeatureBentoGrid, SectionTitle, type BentoFeature, type BentoSpan, type FeatureBentoSectionProps }
```

### `registry/miseui/blocks/feature-bento-panels.tsx`

```tsx
import type * as React from "react"
import { useState } from "react"
import { Check } from "lucide-react"
import { cn } from "@/registry/miseui/lib/utils"
import { Badge } from "@/registry/miseui/ui/badge"
import { Button } from "@/registry/miseui/ui/button"
import { Card } from "@/registry/miseui/ui/card"
import { GooeyMorph } from "@/registry/miseui/ui/gooey-morph"
import { Input } from "@/registry/miseui/ui/input"
import type { BentoFeature } from "./feature-bento-grid"

/** Everything but the beam reads the accent from this, so the panels stay neutral and the beam stays the one accent. */
const NEUTRAL_ACCENT = { "--mise": "var(--foreground)" } as React.CSSProperties
const SURFACE_CLASS = "w-full max-w-md gap-4 rounded-2xl bg-background p-4 shadow-none @2xl:p-6 dark:bg-muted"

const OWNERS = [
  { owner: "Priya", task: "Send the revised tiers to sales" },
  { owner: "Tomas", task: "Draft the customer email" },
  { owner: "Wen", task: "Brief support on the January move" },
] as const

const SPEAKERS = [
  { id: "priya", label: "Priya" },
  { id: "tomas", label: "Tomas" },
  { id: "wen", label: "Wen" },
  { id: "ada", label: "Ada" },
  { id: "noor", label: "Noor" },
]

const DESTINATIONS = [
  { id: "email", label: "Email", target: "5 people" },
  { id: "chat", label: "Chat", target: "#pricing" },
  { id: "tracker", label: "Tracker", target: "3 tasks" },
] as const

const THREADS = [
  { speaker: "Priya", point: "Drop the $9 tier." },
  { speaker: "Tomas", point: "Move people to Starter in January." },
  { speaker: "Wen", point: "Tell support first." },
] as const

const MERGED_RECORD = "Drop the $9 tier, move people to Starter in January, and tell support first."
const DOT_PX = 24
const DOT_GAP_PX = 16

function SummaryPanel() {
  return (
    <Card effect="beam" className={SURFACE_CLASS}>
      <div className="flex flex-wrap items-center justify-between gap-2 text-[13px]">
        <span>Pricing review</span>
        <span className="text-muted-foreground">Finished 2 min after the call</span>
      </div>
      <ul className="flex flex-col divide-y border-t">
        {OWNERS.map((item) => (
          <li key={item.owner} className="flex items-center gap-3 py-2 text-[13px] leading-relaxed">
            <Badge variant="secondary" className="font-normal">
              {item.owner}
            </Badge>
            <span className="min-w-0 flex-1 text-pretty">{item.task}</span>
          </li>
        ))}
      </ul>
    </Card>
  )
}

/** Read-only on purpose: this is the shape of the feature, and the block does not ship a search backend. */
function AskPanel() {
  return (
    <Card className={SURFACE_CLASS}>
      <Input readOnly tabIndex={-1} aria-label="Question asked of the call" value="Who said January?" className="bg-transparent" />
      <blockquote className="flex flex-col gap-2 border-t pt-4">
        <p className="text-[15px] leading-relaxed text-pretty">We move everyone to Starter in January, not before.</p>
        <footer className="text-[13px] text-muted-foreground">Priya, at 14:02</footer>
      </blockquote>
    </Card>
  )
}

/** Moves only when pressed: the merge is the point of the feature, so the person makes it happen. */
function ThreadsPanel() {
  const [merged, setMerged] = useState(false)
  return (
    <Card className={SURFACE_CLASS} style={NEUTRAL_ACCENT}>
      <div className="flex flex-wrap items-center justify-between gap-4">
        <GooeyMorph items={SPEAKERS} merged={merged} size={DOT_PX} gap={DOT_GAP_PX} />
        <Button variant="outline" aria-pressed={merged} onClick={() => setMerged((current) => !current)} className="pointer-coarse:h-10">
          {merged ? "Split by speaker" : "Merge the threads"}
        </Button>
      </div>
      {merged ? (
        <p data-testid="merged-record" className="border-t pt-4 text-[15px] leading-relaxed text-pretty">
          {MERGED_RECORD}
        </p>
      ) : (
        <ul className="flex flex-col gap-2 border-t pt-4 text-[13px] leading-relaxed">
          {THREADS.map((thread) => (
            <li key={thread.speaker} className="text-pretty">
              <span className="font-medium">{thread.speaker}</span> <span className="text-muted-foreground">{thread.point}</span>
            </li>
          ))}
        </ul>
      )}
    </Card>
  )
}

function SendPanel() {
  const [sent, setSent] = useState(false)
  return (
    <Card className={SURFACE_CLASS}>
      <ul className="flex flex-col divide-y">
        {DESTINATIONS.map((destination) => (
          <li key={destination.id} className="flex items-center justify-between gap-3 py-2 text-[13px] leading-relaxed">
            <span>{destination.label}</span>
            <span className="flex items-center gap-2 text-muted-foreground">
              {destination.target}
              {sent ? <Check aria-label="Delivered" className="size-4 text-foreground" /> : null}
            </span>
          </li>
        ))}
      </ul>
      <Button disabled={sent} onClick={() => setSent(true)} className="w-full pointer-coarse:h-10">
        {sent ? "Sent to all three" : "Send the summary"}
      </Button>
    </Card>
  )
}

const AFTER_THE_CALL: BentoFeature = {
  id: "after-the-call",
  title: "Keeps working after you hang up.",
  description: "The summary is finished by the time you are back at your desk, with no tab left open.",
  visual: <SummaryPanel />,
}
const ASK: BentoFeature = {
  id: "ask",
  title: "Ask the call anything.",
  description: "You get the quote and the minute it was said.",
  visual: <AskPanel />,
}
const THREADS_FEATURE: BentoFeature = {
  id: "threads",
  title: "Five voices, one page.",
  description: "Everyone's points merge into one record, and split back by speaker when you need to check who said it.",
  visual: <ThreadsPanel />,
}
const SEND: BentoFeature = {
  id: "send",
  title: "Goes where the team already reads.",
  description: "Email, chat or your tracker, whichever people actually open.",
  visual: <SendPanel />,
}

const ROW_FEATURES: readonly BentoFeature[] = [AFTER_THE_CALL, ASK, SEND]
const TAB_FEATURES: readonly BentoFeature[] = [AFTER_THE_CALL, ASK, THREADS_FEATURE, SEND]

/** The rounded grey ground a picture sits on, so a small product piece reads as placed and not as lost in white. */
function PanelPlate({ className, children }: { className?: string; children: React.ReactNode }) {
  return <div className={cn("flex min-h-72 min-w-0 items-center justify-center rounded-2xl bg-muted p-4 @md:p-8 dark:bg-card", className)}>{children}</div>
}

export { PanelPlate, ROW_FEATURES, TAB_FEATURES }
```

### `registry/miseui/blocks/feature-bento-parts.tsx`

```tsx
import type * as React from "react"
import { useState } from "react"
import { Check } from "lucide-react"
import { cn } from "@/registry/miseui/lib/utils"
import { Badge } from "@/registry/miseui/ui/badge"
import { Button } from "@/registry/miseui/ui/button"
import { Card, CardContent } from "@/registry/miseui/ui/card"
import { GooeyMorph } from "@/registry/miseui/ui/gooey-morph"
import { Input } from "@/registry/miseui/ui/input"
import { Materialize } from "@/registry/miseui/ui/materialize"
import { ShimmerText } from "@/registry/miseui/ui/shimmer-text"
import { ThinkingOrb } from "@/registry/miseui/ui/thinking-orb"

/**
 * The accent lives in one place on this grid, the beam. Every other visual reads it from a variable, so
 * pointing the variable at the ink makes them neutral in all palettes and in both themes.
 */
const NEUTRAL_ACCENT = { "--mise": "var(--foreground)" } as React.CSSProperties

/** Where WebGL2 is missing the button paints --mise, which here would add a second accent. Silver keeps it neutral. */
const SILVER_METAL = { "--mise": "oklch(0.86 0.005 270)" } as React.CSSProperties

/** A small real control lying over a card. Tinted from the ink, and off in dark mode, where it would read as a glow. */
const CHIP_CLASS =
  "gap-2 bg-popover px-3 py-1 text-[13px] font-normal shadow-[0_8px_16px_-8px_color-mix(in_oklab,var(--foreground)_24%,transparent)] dark:shadow-none"

const SPEAKERS = [
  { id: "priya", label: "Priya" },
  { id: "tomas", label: "Tomas" },
  { id: "wen", label: "Wen" },
  { id: "ada", label: "Ada" },
  { id: "noor", label: "Noor" },
]

const DOT_PX = 24
const DOT_GAP_PX = 16
const ORB_PX = 56

/** A decisions card peeks from behind the working card, and a status chip lies over its corner. */
function BeamVisual() {
  return (
    <div className="relative isolate w-full pt-8 pb-4">
      <Card
        aria-hidden="true"
        data-testid="bento-back-card"
        className="absolute top-0 right-8 -z-10 h-20 w-1/2 flex-row justify-between gap-3 rounded-2xl bg-muted p-3 text-[13px] text-muted-foreground shadow-none"
      >
        <span>Decisions</span>
        <span>3</span>
      </Card>
      <Card effect="beam" className="w-full gap-3 rounded-2xl bg-card py-4 shadow-none">
        <CardContent className="flex items-center justify-between gap-4 px-4 text-[13px]">
          <ShimmerText>Writing the summary</ShimmerText>
          <span className="shrink-0 text-muted-foreground">Pricing review</span>
        </CardContent>
        <CardContent className="flex items-center gap-2 px-4 pb-4 text-[13px] text-muted-foreground">
          <Check aria-hidden="true" className="size-4 shrink-0" /> Transcribed 38 minutes
        </CardContent>
      </Card>
      <Badge variant="outline" className={cn(CHIP_CLASS, "absolute bottom-0 left-4")}>
        <Check />4 owners found
      </Badge>
    </div>
  )
}

/** Read-only on purpose: this is the shape of the feature, and the block does not ship a search backend. */
function OrbVisual() {
  return (
    <div className="flex w-full flex-col items-center pb-4" style={NEUTRAL_ACCENT}>
      <ThinkingOrb state="thinking" size={ORB_PX} label="Looking through the call" />
      <div className="relative -mt-2 w-full">
        <Input readOnly tabIndex={-1} aria-label="Question asked of the call" value="Who said January?" className="bg-background dark:bg-card" />
        <Badge variant="outline" className={cn(CHIP_CLASS, "absolute top-full right-2 -translate-y-1/2")}>
          Priya, 14:02
        </Badge>
      </div>
    </div>
  )
}

/** Moves only when pressed, so it does not count against the cap on effects running at once. */
function MergeVisual() {
  const [merged, setMerged] = useState(false)
  return (
    <div className="relative w-full pt-3" style={NEUTRAL_ACCENT}>
      <Card className="w-full flex-row flex-wrap items-center justify-between gap-4 rounded-2xl p-4 shadow-none">
        <GooeyMorph items={SPEAKERS} merged={merged} size={DOT_PX} gap={DOT_GAP_PX} />
        <Button variant="outline" size="sm" aria-pressed={merged} onClick={() => setMerged((current) => !current)}>
          {merged ? "Split by speaker" : "Merge the threads"}
        </Button>
      </Card>
      <Badge variant="outline" className={cn(CHIP_CLASS, "absolute top-0 right-4")}>
        {merged ? "1 record" : "5 speakers"}
      </Badge>
    </div>
  )
}

function SendVisual() {
  const [sent, setSent] = useState(false)
  return (
    <div className="relative w-full pb-4">
      <Card className="flex-row flex-wrap items-center justify-between gap-4 rounded-2xl bg-background dark:bg-card p-3 pl-4 shadow-none">
        <span className="text-[13px] text-muted-foreground">Pricing review summary</span>
        <div style={SILVER_METAL}>
          <Button effect="liquid" onClick={() => setSent(true)}>
            {sent ? "Sent to #pricing" : "Send to #pricing"}
          </Button>
        </div>
      </Card>
      <Materialize show={sent} origin="0% 100%" className="absolute bottom-0 left-4">
        <Badge variant="outline" className={CHIP_CLASS}>
          <Check />
          Delivered to 5 people
        </Badge>
      </Materialize>
    </div>
  )
}

export { BeamVisual, MergeVisual, OrbVisual, SendVisual }
```

### `registry/miseui/blocks/feature-bento-rows.tsx`

```tsx
import { cn } from "@/registry/miseui/lib/utils"
import { SectionTitle, type BentoFeature, type FeatureBentoSectionProps } from "./feature-bento-grid"
import { PanelPlate, ROW_FEATURES } from "./feature-bento-panels"

function FeatureRow({ feature, flipped }: { feature: BentoFeature; flipped: boolean }) {
  return (
    <li data-slot="feature-bento-row" className="grid grid-cols-[minmax(0,1fr)] items-center gap-6 @3xl:grid-cols-2 @3xl:gap-12">
      {/* Inline, so one tone-split sentence reads as a single thought while the heading stays a real heading. */}
      <div data-testid="feature-bento-copy" className={cn("text-display min-w-0 max-w-md text-2xl leading-[1.12]! text-pretty @4xl:text-3xl", !feature.visual && "@3xl:col-span-2")}>
        <h3 className="inline">{feature.title}</h3> <p className="inline text-muted-foreground">{feature.description}</p>
      </div>
      {feature.visual ? (
        // The order flips only side by side: stacked, text comes first on every row, so the eye keeps one path down the page.
        <PanelPlate className={cn(flipped && "@3xl:order-first")}>{feature.visual}</PanelPlate>
      ) : null}
    </li>
  )
}

function FeatureBentoRows({
  title = "After you hang up.",
  titleQuiet = "The notes take care of themselves.",
  features = ROW_FEATURES,
  className,
  ...props
}: FeatureBentoSectionProps) {
  return (
    <section data-slot="feature-bento" data-variant="rows" className={cn("@container w-full bg-background pointer-coarse:[&_input]:h-10", className)} {...props}>
      <div className="mx-auto flex max-w-6xl flex-col gap-12 px-4 py-16 @2xl:px-8">
        <SectionTitle title={title} quiet={titleQuiet} />
        {features.length > 0 ? (
          <ul className="flex flex-col gap-12">
            {features.map((feature, index) => (
              <FeatureRow key={feature.id} feature={feature} flipped={index % 2 === 1} />
            ))}
          </ul>
        ) : null}
      </div>
    </section>
  )
}

export { FeatureBentoRows }
```

### `registry/miseui/blocks/feature-bento-tabs.tsx`

```tsx
import { motion, useReducedMotion } from "motion/react"
import { SPRING_DEFAULT } from "@/registry/miseui/lib/motion"
import { cn } from "@/registry/miseui/lib/utils"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/registry/miseui/ui/tabs"
import { SectionTitle, type BentoFeature, type FeatureBentoSectionProps } from "./feature-bento-grid"
import { PanelPlate, TAB_FEATURES } from "./feature-bento-panels"

const PANEL_START = { opacity: 0, y: 8 }
const PANEL_END = { opacity: 1, y: 0 }

function FeatureTab({ feature }: { feature: BentoFeature }) {
  return (
    <TabsTrigger
      value={feature.id}
      className="group h-auto min-h-10 w-full flex-none flex-col items-start gap-1 rounded-xl p-4 text-start whitespace-normal data-[state=active]:bg-muted data-[state=active]:shadow-none dark:data-[state=active]:bg-card"
    >
      <span className="text-[15px] leading-snug font-medium">{feature.title}</span>
      {/* Only the open one says more: the list stays short enough to read as a choice. */}
      <span className="hidden text-[13px] leading-relaxed font-normal text-pretty text-muted-foreground group-data-[state=active]:block">{feature.description}</span>
    </TabsTrigger>
  )
}

/** Renders only when opened, which is what plays the entrance, and means a closed panel's effects are not running. */
function FeaturePanel({ feature }: { feature: BentoFeature }) {
  const reduceMotion = useReducedMotion()
  return (
    <TabsContent value={feature.id} className="w-full min-w-0 rounded-xl focus-visible:ring-[3px] focus-visible:ring-ring/50">
      <motion.div
        className="flex w-full justify-center"
        initial={reduceMotion ? false : PANEL_START}
        animate={PANEL_END}
        transition={SPRING_DEFAULT}
      >
        {feature.visual ?? <p className="text-display max-w-md text-2xl leading-[1.12]! text-pretty">{feature.description}</p>}
      </motion.div>
    </TabsContent>
  )
}

function FeatureBentoTabs({
  title = "After you hang up.",
  titleQuiet = "The notes take care of themselves.",
  features = TAB_FEATURES,
  defaultFeatureId,
  className,
  ...props
}: FeatureBentoSectionProps & { defaultFeatureId?: string }) {
  return (
    <section data-slot="feature-bento" data-variant="tabs" className={cn("@container w-full bg-background pointer-coarse:[&_input]:h-10", className)} {...props}>
      <div className="mx-auto flex max-w-6xl flex-col gap-8 px-4 py-16 @2xl:gap-12 @2xl:px-8">
        <SectionTitle title={title} quiet={titleQuiet} />
        {features.length > 0 ? (
          <Tabs
            defaultValue={defaultFeatureId ?? features[0].id}
            orientation="vertical"
            className="grid grid-cols-[minmax(0,1fr)] gap-4 @3xl:grid-cols-[minmax(0,2fr)_minmax(0,3fr)] @3xl:gap-8"
          >
            <TabsList aria-label="Features" className="h-auto w-full flex-col items-stretch gap-1 rounded-none bg-transparent p-0">
              {features.map((feature) => (
                <FeatureTab key={feature.id} feature={feature} />
              ))}
            </TabsList>
            {/* Above the list while stacked: the thumb is on the list, and the change has to happen where the eye can see it. */}
            <PanelPlate className="order-first min-h-80 @3xl:order-none @3xl:min-h-96">
              {features.map((feature) => (
                <FeaturePanel key={feature.id} feature={feature} />
              ))}
            </PanelPlate>
          </Tabs>
        ) : null}
      </div>
    </section>
  )
}

export { FeatureBentoTabs }
```
