# Features 6: tabbed showcase

> Pill tabs with a sliding indicator switch between feature panels: heading, checked points and link beside a product picture. Tabs scroll sideways on phones.

A shadcn/ui block from the [@ballmac](https://ui.ballmac.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/ballmac/features-6).

- Page, with a live preview: https://shadcn.exchange/r/ballmac/features-6
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/ballmac/features-6-light-8f329e5a44c49153.png
- Registry item: https://ui.ballmac.com/r/features-6.json
- Categories: blocks, features
- Listed: 2026-10-14, source last changed 2026-10-14

## Install

```bash
npx shadcn@latest add https://ui.ballmac.com/r/features-6.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`
- Registry items: `utils`, `https://ui.ballmac.com/r/animated-tabs.json`, `https://ui.ballmac.com/r/badge.json`, `https://ui.ballmac.com/r/media.json`

## Files

### `registry/ballmac/components/blocks/features-6/features-6.tsx`

```tsx
// Ballmac UI: Features 6. https://ui.ballmac.com/blocks/features-6
import * as React from "react"
import { ArrowRight, BarChart3, Check, MessageSquare, Plug, ShieldCheck } from "lucide-react"

import { AnimatedTabs, AnimatedTabsContent, AnimatedTabsList, AnimatedTabsTrigger } from "@/components/ballmac/animated-tabs"
import { Badge } from "@/components/ballmac/badge"
import { cn } from "@/lib/utils"
import { Media, type MediaSource } from "@/components/ballmac/media"

type Features6Tab = {
  /** Stable key for the tab. */
  value: string
  /** Short tab label. */
  label: string
  /** Icon shown before the label. */
  icon?: React.ReactNode
  /** Panel heading. */
  title: string
  /** One or two sentences. */
  description: string
  /** Short checked points. */
  points?: string[]
  /** Link under the points. */
  link?: { label: string; href: string }
  /** Sample picture on the right of the panel. Decorative: the text must say what it shows. Replaced by `image` when you set one. */
  visual?: React.ReactNode
  /** Your own picture for this tab: an image URL (give it imageAlt), an object with alt text and a dark-mode file, or your own element. */
  image?: MediaSource
  /** Describes `image` when it is a plain URL. */
  imageAlt?: string
}

type Features6Props = Omit<React.ComponentProps<"section">, "title"> & {
  /** Section heading. */
  title?: string
  /** One sentence under the heading. */
  description?: string
  /** The tabs, in order. */
  tabs?: Features6Tab[]
  /** Tab selected first. Defaults to the first tab. */
  defaultValue?: string
}

const tile = "bg-card w-full max-w-sm rounded-2xl border p-5 shadow-[0_30px_70px_-35px_rgb(0_0_0/0.4)]"

function AnalyticsVisual() {
  const bars = [38, 52, 44, 66, 58, 79, 72, 91]
  return (
    <div className={tile}>
      <div className="flex items-start justify-between">
        <div>
          <p className="text-muted-foreground text-xs">Weekly active users</p>
          <p className="mt-1 text-3xl font-semibold tracking-tight">24,810</p>
        </div>
        <Badge status="success">+18.2%</Badge>
      </div>
      <div className="mt-6 flex h-32 items-end gap-2">
        {bars.map((h, i) => (
          <div
            key={i}
            className={cn("flex-1 rounded-t-md", i === bars.length - 1 ? "bg-chart-1" : "bg-chart-1/30")}
            style={{ height: `${h}%` }}
          />
        ))}
      </div>
      <div className="text-muted-foreground mt-2 flex justify-between text-[11px]">
        <span>Aug 3</span>
        <span>Sep 21</span>
      </div>
    </div>
  )
}

function CollabVisual() {
  return (
    <div className={tile}>
      <div className="space-y-4">
        {[
          ["MK", "Maya", "Can we move the launch to the 14th?", "bg-chart-1/25"],
          ["JO", "Jonas", "Yes. I’ll update the plan and ping design.", "bg-chart-3/25"],
        ].map(([i, n, t, c]) => (
          <div key={n} className="flex gap-3">
            <span className={cn("text-foreground flex size-8 shrink-0 items-center justify-center rounded-full text-[11px] font-semibold", c)}>{i}</span>
            <div>
              <p className="text-sm font-medium">{n} <span className="text-muted-foreground font-normal">· just now</span></p>
              <p className="text-muted-foreground mt-0.5 text-sm">{t}</p>
            </div>
          </div>
        ))}
      </div>
      <div className="text-muted-foreground mt-5 flex items-center gap-2 rounded-xl border px-3 py-2.5 text-sm">
        <span className="bg-chart-1 h-4 w-px animate-pulse motion-reduce:animate-none" /> Amara is typing…
      </div>
    </div>
  )
}

function SecurityVisual() {
  const rows = [
    ["Signed in with SSO", "Maya K.", "2m ago"],
    ["Role changed to Admin", "Jonas O.", "1h ago"],
    ["API key created", "Amara S.", "3h ago"],
    ["Export downloaded", "Maya K.", "Yesterday"],
  ]
  return (
    <div className={tile}>
      <p className="text-sm font-medium">Audit log</p>
      <ul className="mt-3 divide-y">
        {rows.map(([a, w, t]) => (
          <li key={a} className="flex items-center justify-between gap-3 py-2.5 text-sm">
            <div className="min-w-0">
              <p className="truncate">{a}</p>
              <p className="text-muted-foreground text-xs">{w}</p>
            </div>
            <span className="text-muted-foreground shrink-0 text-xs">{t}</span>
          </li>
        ))}
      </ul>
    </div>
  )
}

function IntegrationsVisual() {
  const apps = [
    ["Slack", true],
    ["GitHub", true],
    ["Linear", true],
    ["Notion", false],
    ["Stripe", true],
    ["Zapier", false],
  ] as const
  return (
    <div className={tile}>
      <p className="text-sm font-medium">Connected apps</p>
      <div className="mt-4 grid grid-cols-3 gap-2.5">
        {apps.map(([name, on]) => (
          <div key={name} className="flex flex-col items-center gap-2 rounded-xl border p-3 text-center">
            <span className="bg-muted flex size-9 items-center justify-center rounded-lg text-sm font-semibold">{name[0]}</span>
            <span className="text-xs">{name}</span>
            <span className={cn("flex items-center gap-1 text-[11px]", on ? "text-chart-2" : "text-muted-foreground")}>
              {on ? <><Check className="size-3" /> Connected</> : "Connect"}
            </span>
          </div>
        ))}
      </div>
    </div>
  )
}

const defaultTabs: Features6Tab[] = [
  {
    value: "analytics",
    label: "Analytics",
    icon: <BarChart3 />,
    title: "Know what’s working, the same day.",
    description: "Dashboards update as events arrive. Break any chart down by plan, country or campaign without writing a query.",
    points: ["Real-time dashboards", "Cohorts and funnels", "Scheduled email reports"],
    link: { label: "Explore analytics", href: "#" },
    visual: <AnalyticsVisual />,
  },
  {
    value: "collaboration",
    label: "Collaboration",
    icon: <MessageSquare />,
    title: "Decide together, in context.",
    description: "Comment on anything, mention a teammate and keep the whole conversation next to the work it is about.",
    points: ["Threaded comments", "Mentions and reminders", "Live cursors and presence"],
    link: { label: "See collaboration", href: "#" },
    visual: <CollabVisual />,
  },
  {
    value: "security",
    label: "Security",
    icon: <ShieldCheck />,
    title: "Enterprise controls without the enterprise setup.",
    description: "Single sign-on, roles and a complete audit trail are included, so security reviews stop blocking your launch.",
    points: ["SAML SSO and SCIM", "Role-based access", "Exportable audit log"],
    link: { label: "Read the security overview", href: "#" },
    visual: <SecurityVisual />,
  },
  {
    value: "integrations",
    label: "Integrations",
    icon: <Plug />,
    title: "Plays well with your stack.",
    description: "Connect the tools your team already lives in. Data flows both ways and stays in sync.",
    points: ["80+ native integrations", "Webhooks and a REST API", "Two-way sync"],
    link: { label: "Browse integrations", href: "#" },
    visual: <IntegrationsVisual />,
  },
]

function Features6({
  title = "Everything your team needs, in one place.",
  description = "Pick a topic to see how it works.",
  tabs = defaultTabs,
  defaultValue,
  className,
  ...props
}: Features6Props) {
  return (
    <section data-slot="features-6" className={cn("mx-auto max-w-6xl px-4 py-20 sm:px-6 md:py-28", className)} {...props}>
      <div className="mx-auto max-w-2xl text-center">
        <h2 className="text-3xl font-semibold tracking-[-0.035em] text-balance sm:text-4xl lg:text-5xl">{title}</h2>
        <p className="text-muted-foreground mt-4 text-lg text-pretty">{description}</p>
      </div>
      <AnimatedTabs defaultValue={defaultValue ?? tabs[0]?.value} className="mt-10 md:mt-14">
        <div className="-mx-4 flex justify-center overflow-x-auto px-4 [scrollbar-width:none]">
          <AnimatedTabsList aria-label="Feature areas" className="h-11">
            {tabs.map((t) => (
              <AnimatedTabsTrigger key={t.value} value={t.value} className="px-4">
                {t.icon}
                {t.label}
              </AnimatedTabsTrigger>
            ))}
          </AnimatedTabsList>
        </div>
        {tabs.map((t) => (
          <AnimatedTabsContent key={t.value} value={t.value} className="mt-8">
            <div className="bg-card grid overflow-hidden rounded-3xl border md:grid-cols-[1fr_1.05fr]">
              <div className="flex flex-col justify-center p-6 sm:p-10 lg:p-14">
                <h3 className="text-2xl font-semibold tracking-[-0.03em] text-balance sm:text-3xl">{t.title}</h3>
                <p className="text-muted-foreground mt-4 text-base leading-relaxed text-pretty">{t.description}</p>
                {t.points && (
                  <ul className="mt-6 space-y-2.5 text-sm">
                    {t.points.map((p) => (
                      <li key={p} className="flex items-start gap-2.5">
                        <Check className="text-chart-2 mt-0.5 size-4 shrink-0" aria-hidden="true" />
                        {p}
                      </li>
                    ))}
                  </ul>
                )}
                {t.link && (
                  <a href={t.link.href} className="group/link focus-visible:ring-ring/50 mt-8 inline-flex w-fit items-center gap-1.5 rounded-md text-sm font-medium outline-none focus-visible:ring-[3px]">
                    {t.link.label}
                    <ArrowRight className="size-4 transition-transform group-hover/link:translate-x-0.5 motion-reduce:transition-none rtl:rotate-180 rtl:group-hover/link:-translate-x-0.5" aria-hidden="true" />
                  </a>
                )}
              </div>
              <div aria-hidden={t.image ? undefined : true} className="bg-muted/40 relative flex items-center justify-center border-t p-6 sm:p-10 md:border-t-0 md:border-s">
                <div className="absolute inset-0 bg-[radial-gradient(color-mix(in_oklch,var(--foreground)_14%,transparent)_1px,transparent_1px)] [background-size:18px_18px] [mask-image:radial-gradient(70%_70%_at_50%_50%,black,transparent)]" />
                <div className="relative flex w-full justify-center">{t.image ? <Media media={t.image} alt={t.imageAlt} fit="contain" className="w-full rounded-xl" /> : t.visual}</div>
              </div>
            </div>
          </AnimatedTabsContent>
        ))}
      </AnimatedTabs>
    </section>
  )
}

export { Features6, type Features6Props, type Features6Tab }
```
