Skip to content

Features Tabs

Tabbed feature explorer: ruled mono tab bar (ink active tab), then a big split panel with title, description and square-bullet list on the left and a stacked mock preview of the channel on the right.

brutalist/section/features-tabs
Open ↗

Source

"use client";

import { useId, useRef, useState, type KeyboardEvent } from "react";
import { cn } from "@/lib/utils";

export interface FeaturesTabsItem {
  label: string;
  title: string;
  description: string;
  bullets: string[];
  /** Lines shown in the mock panel on the right. */
  preview: string[];
}

export interface FeaturesTabsProps {
  kicker?: string;
  title?: string;
  tabs?: FeaturesTabsItem[];
  className?: string;
}

const DEFAULT_TABS: FeaturesTabsItem[] = [
  {
    label: "Changelog",
    title: "A public page worth bookmarking",
    description: "Fast, searchable, on your domain, with RSS and permalinks for every release.",
    bullets: ["Custom domain & theming", "Search & tag filters", "RSS / Atom / JSON feed"],
    preview: ["changelog.acme.dev", "v4.2.0 — AI summaries", "v4.1.3 — Slack digest fix", "v4.1.0 — 3× faster widget"],
  },
  {
    label: "Widget",
    title: "Announcements inside your product",
    description: "A 9 kB launcher that shows what's new exactly where users work.",
    bullets: ["Unread badge counts", "Targeting by plan or role", "Deep links to docs"],
    preview: ["<ShipyardWidget />", "● 3 unread updates", "→ Try AI summaries", "→ New billing page"],
  },
  {
    label: "Digests",
    title: "Email and Slack without the busywork",
    description: "Weekly or per-release digests to subscribers and your own #releases channel.",
    bullets: ["8,000+ subscriber lists", "Localized in 24 languages", "Slack & Teams"],
    preview: ["#releases", "Shipyard bot · 09:00", "This week: 3 releases", "Open rate 61%"],
  },
];

export function FeaturesTabs({ kicker = "[ 03 ] One release, every channel", title = "Pick a channel. Or all of them.", tabs = DEFAULT_TABS, className }: FeaturesTabsProps) {
  const [active, setActive] = useState(0);
  const baseId = useId();
  const refs = useRef<(HTMLButtonElement | null)[]>([]);
  const tab = tabs[active] ?? tabs[0];

  function onKeyDown(e: KeyboardEvent<HTMLDivElement>) {
    const delta = e.key === "ArrowRight" ? 1 : e.key === "ArrowLeft" ? -1 : 0;
    if (!delta) return;
    e.preventDefault();
    const next = (active + delta + tabs.length) % tabs.length;
    setActive(next);
    refs.current[next]?.focus();
  }

  if (!tab) return null;

  return (
    <section className={cn("da-section da-stroke-b bg-da-bg text-da-fg", className)} aria-labelledby={`${baseId}-title`}>
      <div className="da-container">
        <p className="font-da-mono text-xs font-bold tracking-da-label text-da-muted-fg uppercase">{kicker}</p>
        <h2 id={`${baseId}-title`} className="mt-3 font-da-display text-[clamp(2rem,5vw,3.5rem)] leading-[0.95] tracking-da-display uppercase">
          {title}
        </h2>

        <div role="tablist" aria-label="Channels" onKeyDown={onKeyDown} className="da-stroke mt-10 inline-flex flex-wrap bg-da-surface">
          {tabs.map((t, i) => (
            <button
              key={t.label}
              ref={(el) => {
                refs.current[i] = el;
              }}
              id={`${baseId}-tab-${i}`}
              type="button"
              role="tab"
              aria-selected={i === active}
              aria-controls={`${baseId}-panel`}
              tabIndex={i === active ? 0 : -1}
              onClick={() => setActive(i)}
              className={cn(
                "da-focus da-transition px-5 py-3 font-da-mono text-sm font-bold tracking-da-label uppercase",
                i > 0 && "da-stroke-l",
                i === active ? "bg-da-fg text-da-bg" : "text-da-surface-fg hover:bg-da-primary hover:text-da-primary-fg",
              )}
            >
              {t.label}
            </button>
          ))}
        </div>

        <div
          id={`${baseId}-panel`}
          role="tabpanel"
          aria-labelledby={`${baseId}-tab-${active}`}
          className="da-stroke mt-6 grid bg-da-surface text-da-surface-fg shadow-da-lg lg:grid-cols-2"
        >
          <div className="p-8 sm:p-10">
            <h3 className="font-da-display text-3xl leading-none tracking-da-display uppercase">{tab.title}</h3>
            <p className="mt-4 text-lg text-da-muted-fg">{tab.description}</p>
            <ul className="mt-6 space-y-3">
              {tab.bullets.map((b) => (
                <li key={b} className="flex items-center gap-3 font-bold">
                  <span aria-hidden className="da-stroke size-4 bg-da-primary" />
                  {b}
                </li>
              ))}
            </ul>
          </div>
          <div aria-hidden className="da-stroke-t lg:da-stroke-l flex flex-col justify-center gap-3 bg-da-surface-2 p-8 font-da-mono text-sm lg:border-t-0">
            {tab.preview.map((line, i) => (
              <p key={line} className={cn("da-stroke bg-da-surface px-4 py-3 font-bold", i === 0 && "bg-da-fg text-da-bg")}>
                {line}
              </p>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
}

export default FeaturesTabs;

modules/brutalist/section/features-tabs/index.tsx

Props

PropTypeDefaultDescription
kickerstring—Mono label.
titlestring—Section title (h2).
tabsFeaturesTabsItem[]—{ label, title, description, bullets[], preview[] }[].
classNamestring—Classes on the <section>.

Other features variants in Brutalist

Features in other art directions

Features Bento

Glass bento grid on 6 columns: a large AI-recap tile with a mock summary and owner chips, plus five smaller frosted tiles with gradient squircle icons.

GlassFeatures

Features Glass Grid

Centered header and six frosted feature cards with gradient squircle icons; cards lift with an overshoot and reveal a soft corner glow on hover.

GlassFeatures

Features Orbit

Integrations section: copy with a chip list on one side, and a central gradient orb with integration bubbles orbiting on two counter-rotating glass rings (bubbles stay upright). Pure CSS.

GlassFeatures

Features Tabs

Glass pill tab bar (Radix Tabs) with gradient active tab switching a large frosted panel: title, copy and bullets on the left, a layered glass preview on the right.

GlassFeatures

Features Alternating

Three zig-zag rows, each pairing a numbered kicker, title, copy and link with an HTML product mock-up (routing rules, on-call week, AI postmortem) on a soft tinted plate.

MinimalFeatures

Features Bento

Bento grid of five hairline tiles on a 6-column layout, each with a small token-themed HTML illustration (routing lines, uptime bars, MTTR metric, on-call week) above an icon, title and description.

MinimalFeatures

Features List

Dense four-column feature list: each item has a small indigo line icon, a title and a one-sentence description under a hairline top rule. Eight defaults, left-aligned header.

MinimalFeatures

Features Steps

Five-step workflow (detect, page, coordinate, resolve, learn) laid out horizontally on desktop and vertically on mobile, joined by a hairline, with numbered nodes and timing labels.

MinimalFeatures

Features Index

Features as a numbered index: a label + headline in the left columns, and a two-column ruled list on the right where each item is “01 — Title” with a short description.

Mono CleanFeatures

Features Numbered

Three large numbered steps: giant outlined-looking numerals in muted ink above a hairline, step title and text; columns on desktop, stacked on mobile.

Mono CleanFeatures

Features Showcase

Work showcase as the feature: an asymmetric grid (fixed row heights; wide, tall, square and full-width spans) of neutral gray image frames with mono captions (studio — project, template used); captions underline on hover.

Mono CleanFeatures

Features Specs

Technical spec sheet: grouped key/value definition lists in a 4-column grid, each group under a full-ink rule with a mono heading — reads like a product data sheet.

Mono CleanFeatures

Features Bento

Data-driven bento grid of bordered cards: cash-flow line chart, DSO metric, approval flow, currency list and integrations — each a small realistic mock.

Neo CorporateFeatures

Features Grid

Classic 3×2 bordered feature grid sharing hairline borders (one outer frame), each cell with a blue icon square, title, text and a “Learn more” link.

Neo CorporateFeatures

Features Tabs

Underline tabs (Radix Tabs) switching between product areas: copy + check points on the left, a bordered data card (status rows) on the right.

Neo CorporateFeatures

Features Versus

Before/after comparison: a muted “spreadsheets and email” column with red crosses next to a blue-bordered “with Ledgerly” column with green checks.

Neo CorporateFeatures

Features Alternating

Alternating zig-zag rows: copy with check points on one side, a soft blob-shaped tinted panel holding a small product vignette (suppliers, actions, report) on the other.

Organic SoftFeatures

Features Bento

Soft bento: rounded tinted tiles of mixed sizes — a monthly emissions bar chart, a human equivalence (“= 42 flights”), supplier avatars, a certifications tile and a leaf quote.

Organic SoftFeatures

Features Cards

Three tall rounded cards in sage, clay and paper tints; each has an icon sitting in an organic blob, a serif title and calm copy.

Organic SoftFeatures

Features Journey

Horizontal journey: numbered circles connected by a hand-drawn wavy dashed path (vertical list on mobile), each step with a serif title, text and a mono time estimate.

Organic SoftFeatures

Features Checklist

Interactive split section: copy and a mint progress bar on the left, a white card of four round-checkbox steps on the right that turn mint with a bouncy tick when clicked; celebration at 100%.

Soft FlatFeatures

Features Icons

Centered 4-column grid of eight small features, each with a large round pastel icon badge (colors rotate), rounded title and one-liner.

Soft FlatFeatures

Features Pastel

2×2 grid of large pastel feature cards (peach, mint, lavender, butter), each with icon, rounded title, copy and a tiny flat illustration (mini board, bar week, reminder toast, chat bubbles).

Soft FlatFeatures

Features Steps

Three numbered step cards in peach, lavender and mint joined by a dashed path on desktop, each with a white emoji medallion, mono step label, title and text.

Soft FlatFeatures