25 Tailwind CSS Button Groups
07 / 25

All 25 in 25 Tailwind CSS Button Groups

  1. 01Tailwind Split Button with Anchor-Positioned Popover Dropdown (No JS)
  2. 02Tailwind Segmented Control with Motion-Path Comet Indicator
  3. 03Tailwind Toggle Button Group with View Transitions Morphing Highlight
  4. 04Tailwind Pricing Toggle — Monthly / Yearly Billing Switch (No JS)
  5. 05Tailwind Segmented Control with Sliding Thumb (iOS Style)
  6. 06Tailwind Split Button Dropdown with Native Details Menu (Save Options)
  7. 07Tailwind Icon Button Group Toolbar with Tooltips (Rich Text Editor)
  8. 08Tailwind Pill Filter Button Group with Sliding Indicator (Anchor Positioning)
  9. 09Tailwind Toggle Button Group for Text Alignment (Radio Inputs, Live Preview)
  10. 10Tailwind Web3 Connect Wallet Button Grid (Wallet Sign-In Modal)
  11. 11Tailwind AI Model Selector Button Group (LLM Model Picker with Badges)
  12. 12Tailwind Liquid Metal Segmented Control (Animated Chrome Thumb)
  13. 13Tailwind Tab Button Group with Subgrid Underline Indicator
  14. 14Tailwind Multi-Select Chip Buttons (Tag Filter Checkboxes with Count)
  15. 15Tailwind Animated Split Button Deploy Menu (Staggered Dropdown Reveal)
  16. 16Tailwind Radio Button Group with Conic Fill Animation (Export Format Picker)
  17. 17Tailwind Retro LED Pixel Button Group (Dot-Matrix Display Buttons)
  18. 18Tailwind Hold-to-Confirm Button Group with Gradient Progress (Press and Hold)
  19. 19Tailwind Holographic Icon Segmented Control (Grid / List View Switcher)
  20. 20Tailwind 3D Vertical Button Group with Hover Depth (Perspective Stack)
  21. 21Tailwind Floating Action Button with Radial Speed Dial Menu (No JS)
  22. 22Tailwind Glitch Effect Button Group (Cyberpunk RGB-Split Audio Filter)
  23. 23Tailwind Animated Gradient Border Pill Buttons (Rotating Conic Ring)
  24. 24Tailwind Button Group with Ripple Click Effect (Radar Shockwave Feedback)
  25. 25Tailwind Button Group with Cursor-Following Spotlight Glow (Date Range Presets)
Tailwind + JSMIT licensed

Tailwind Icon Button Group Toolbar with Tooltips (Rich Text Editor)

A rich-text formatting toolbar: icon buttons with CSS-only tooltips (after:content-[attr(data-tip)]) that also appear on keyboard focus, real toggle state via aria-pressed:, and a sample paragraph that previews bold, italic and underline as you press them. A short controller adds toolbar arrow-key navigation and the toggles themselves.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="tbg-07 w-full min-h-svh grid place-items-center bg-tbg07-bg bg-linear-150 from-tbg07-bg to-tbg07-bg2 px-5 py-14 font-tbg07-sans text-tbg07-ink" aria-label="Icon button group toolbar demo">
  <div class="group/ed w-full max-w-[34rem] rounded-2xl bg-tbg07-bar shadow-tbg07-lift">
    <div data-tbg07-bar role="toolbar" aria-label="Text formatting" class="flex flex-wrap items-center gap-0.5 border-b border-tbg07-line p-1.5">
      <button type="button" data-cmd="bold" aria-label="Bold" aria-pressed="true" data-tip="Bold · ⌘B" tabindex="0" class="relative grid size-11 cursor-pointer place-items-center rounded-xl text-tbg07-mut transition-colors hover:bg-tbg07-hover hover:text-tbg07-ink aria-pressed:bg-tbg07-acc aria-pressed:text-tbg07-on focus-visible:z-1 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg07-acc-d before:pointer-events-none before:absolute before:bottom-[calc(100%+4px)] before:left-1/2 before:-translate-x-1/2 before:border-[5px] before:border-transparent before:border-t-tbg07-ink before:opacity-0 before:transition-opacity before:content-[''] hover:before:opacity-100 focus-visible:before:opacity-100 after:pointer-events-none after:absolute after:bottom-[calc(100%+14px)] after:left-1/2 after:-translate-x-1/2 after:rounded-lg after:bg-tbg07-ink after:px-2.5 after:py-1.5 after:text-xs after:font-semibold after:whitespace-nowrap after:text-tbg07-tip after:opacity-0 after:transition-[opacity,translate] after:content-[attr(data-tip)] hover:after:-translate-y-0.5 hover:after:opacity-100 focus-visible:after:opacity-100 motion-reduce:after:transition-none"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linejoin="round" aria-hidden="true"><path d="M7 5h6a3.5 3.5 0 0 1 0 7H7zM7 12h7a3.5 3.5 0 0 1 0 7H7z"/></svg></button>
      <button type="button" data-cmd="italic" aria-label="Italic" aria-pressed="false" data-tip="Italic · ⌘I" tabindex="-1" class="relative grid size-11 cursor-pointer place-items-center rounded-xl text-tbg07-mut transition-colors hover:bg-tbg07-hover hover:text-tbg07-ink aria-pressed:bg-tbg07-acc aria-pressed:text-tbg07-on focus-visible:z-1 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg07-acc-d before:pointer-events-none before:absolute before:bottom-[calc(100%+4px)] before:left-1/2 before:-translate-x-1/2 before:border-[5px] before:border-transparent before:border-t-tbg07-ink before:opacity-0 before:transition-opacity before:content-[''] hover:before:opacity-100 focus-visible:before:opacity-100 after:pointer-events-none after:absolute after:bottom-[calc(100%+14px)] after:left-1/2 after:-translate-x-1/2 after:rounded-lg after:bg-tbg07-ink after:px-2.5 after:py-1.5 after:text-xs after:font-semibold after:whitespace-nowrap after:text-tbg07-tip after:opacity-0 after:transition-[opacity,translate] after:content-[attr(data-tip)] hover:after:-translate-y-0.5 hover:after:opacity-100 focus-visible:after:opacity-100 motion-reduce:after:transition-none"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M15 5h-5M14 19H9M14 5l-4 14"/></svg></button>
      <button type="button" data-cmd="underline" aria-label="Underline" aria-pressed="false" data-tip="Underline · ⌘U" tabindex="-1" class="relative grid size-11 cursor-pointer place-items-center rounded-xl text-tbg07-mut transition-colors hover:bg-tbg07-hover hover:text-tbg07-ink aria-pressed:bg-tbg07-acc aria-pressed:text-tbg07-on focus-visible:z-1 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg07-acc-d before:pointer-events-none before:absolute before:bottom-[calc(100%+4px)] before:left-1/2 before:-translate-x-1/2 before:border-[5px] before:border-transparent before:border-t-tbg07-ink before:opacity-0 before:transition-opacity before:content-[''] hover:before:opacity-100 focus-visible:before:opacity-100 after:pointer-events-none after:absolute after:bottom-[calc(100%+14px)] after:left-1/2 after:-translate-x-1/2 after:rounded-lg after:bg-tbg07-ink after:px-2.5 after:py-1.5 after:text-xs after:font-semibold after:whitespace-nowrap after:text-tbg07-tip after:opacity-0 after:transition-[opacity,translate] after:content-[attr(data-tip)] hover:after:-translate-y-0.5 hover:after:opacity-100 focus-visible:after:opacity-100 motion-reduce:after:transition-none"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M7 4v7a5 5 0 0 0 10 0V4M6 20h12"/></svg></button>
      <span aria-hidden="true" class="mx-1.5 h-6 w-px bg-tbg07-line"></span>
      <button type="button" data-cmd="link" aria-label="Insert link" data-tip="Link · ⌘K" tabindex="-1" class="relative grid size-11 cursor-pointer place-items-center rounded-xl text-tbg07-mut transition-colors hover:bg-tbg07-hover hover:text-tbg07-ink focus-visible:z-1 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg07-acc-d before:pointer-events-none before:absolute before:bottom-[calc(100%+4px)] before:left-1/2 before:-translate-x-1/2 before:border-[5px] before:border-transparent before:border-t-tbg07-ink before:opacity-0 before:transition-opacity before:content-[''] hover:before:opacity-100 focus-visible:before:opacity-100 after:pointer-events-none after:absolute after:bottom-[calc(100%+14px)] after:left-1/2 after:-translate-x-1/2 after:rounded-lg after:bg-tbg07-ink after:px-2.5 after:py-1.5 after:text-xs after:font-semibold after:whitespace-nowrap after:text-tbg07-tip after:opacity-0 after:transition-[opacity,translate] after:content-[attr(data-tip)] hover:after:-translate-y-0.5 hover:after:opacity-100 focus-visible:after:opacity-100 motion-reduce:after:transition-none"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M10 13a4 4 0 0 0 5.7.6l2.6-2.6a4 4 0 0 0-5.6-5.7l-1.5 1.5M14 11a4 4 0 0 0-5.7-.6L5.7 13a4 4 0 0 0 5.6 5.7l1.5-1.5"/></svg></button>
      <button type="button" data-cmd="code" aria-label="Inline code" aria-pressed="false" data-tip="Code · ⌘E" tabindex="-1" class="relative grid size-11 cursor-pointer place-items-center rounded-xl text-tbg07-mut transition-colors hover:bg-tbg07-hover hover:text-tbg07-ink aria-pressed:bg-tbg07-acc aria-pressed:text-tbg07-on focus-visible:z-1 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg07-acc-d before:pointer-events-none before:absolute before:bottom-[calc(100%+4px)] before:left-1/2 before:-translate-x-1/2 before:border-[5px] before:border-transparent before:border-t-tbg07-ink before:opacity-0 before:transition-opacity before:content-[''] hover:before:opacity-100 focus-visible:before:opacity-100 after:pointer-events-none after:absolute after:bottom-[calc(100%+14px)] after:left-1/2 after:-translate-x-1/2 after:rounded-lg after:bg-tbg07-ink after:px-2.5 after:py-1.5 after:text-xs after:font-semibold after:whitespace-nowrap after:text-tbg07-tip after:opacity-0 after:transition-[opacity,translate] after:content-[attr(data-tip)] hover:after:-translate-y-0.5 hover:after:opacity-100 focus-visible:after:opacity-100 motion-reduce:after:transition-none"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 8l-5 4 5 4M15 8l5 4-5 4"/></svg></button>
      <span aria-hidden="true" class="mx-1.5 h-6 w-px bg-tbg07-line"></span>
      <button type="button" data-cmd="clear" aria-label="Clear formatting" data-tip="Clear formatting" tabindex="-1" class="relative grid size-11 cursor-pointer place-items-center rounded-xl text-tbg07-mut transition-colors hover:bg-tbg07-hover hover:text-tbg07-ink focus-visible:z-1 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg07-acc-d before:pointer-events-none before:absolute before:bottom-[calc(100%+4px)] before:left-1/2 before:-translate-x-1/2 before:border-[5px] before:border-transparent before:border-t-tbg07-ink before:opacity-0 before:transition-opacity before:content-[''] hover:before:opacity-100 focus-visible:before:opacity-100 after:pointer-events-none after:absolute after:bottom-[calc(100%+14px)] after:left-1/2 after:-translate-x-1/2 after:rounded-lg after:bg-tbg07-ink after:px-2.5 after:py-1.5 after:text-xs after:font-semibold after:whitespace-nowrap after:text-tbg07-tip after:opacity-0 after:transition-[opacity,translate] after:content-[attr(data-tip)] hover:after:-translate-y-0.5 hover:after:opacity-100 focus-visible:after:opacity-100 motion-reduce:after:transition-none"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 5h12M10 5L8 19M15 5l-1 5M4 20L20 4"/></svg></button>
    </div>
    <p class="px-6 pt-5 pb-6 font-tbg07-serif text-[19px] leading-relaxed text-tbg07-ink group-has-[[data-cmd=bold][aria-pressed=true]]/ed:font-semibold group-has-[[data-cmd=italic][aria-pressed=true]]/ed:italic group-has-[[data-cmd=underline][aria-pressed=true]]/ed:underline group-has-[[data-cmd=underline][aria-pressed=true]]/ed:decoration-tbg07-acc-d group-has-[[data-cmd=underline][aria-pressed=true]]/ed:underline-offset-4 group-has-[[data-cmd=code][aria-pressed=true]]/ed:font-mono group-has-[[data-cmd=code][aria-pressed=true]]/ed:text-[16px]">Good tools disappear. You notice the work, not the buttons you pressed to make it.</p>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tbg07-bg: #20261c;
  --color-tbg07-bg2: #2c3626;
  --color-tbg07-bar: #f4f7f0;
  --color-tbg07-line: #d8e0ce;
  --color-tbg07-hover: #e5ecdd;
  --color-tbg07-ink: #1a2415;
  --color-tbg07-tip: #eef4e8;
  --color-tbg07-mut: #4a5c3f;
  --color-tbg07-mut: oklch(0.44 0.05 135);
  --color-tbg07-on: #12200a;
  --color-tbg07-acc: #93cf52;
  --color-tbg07-acc: oklch(0.78 0.16 132);
  --color-tbg07-acc-d: #4d8a1c;
  --color-tbg07-acc-d: oklch(0.56 0.14 135);
  --shadow-tbg07-lift: 0 24px 50px -22px rgb(0 0 0 / 0.75), inset 0 0 0 1px rgb(255 255 255 / 0.6);
  --font-tbg07-sans: 'Instrument Sans', system-ui, sans-serif;
  --font-tbg07-serif: 'Newsreader', Georgia, serif;
}
/* Root rule: the full-bleed contract, restated so the stage is sized before the browser build compiles. */

.tbg-07 {
  width: 100%;
  min-height: 100svh;
}
(() => {
  document.querySelectorAll('[data-tbg07-bar]').forEach((bar) => {
    if (bar.dataset.wired) return;
    bar.dataset.wired = '1';
    const btns = [...bar.querySelectorAll('button')];
    const focusAt = (i) => {
      btns.forEach((b, j) => { b.tabIndex = j === i ? 0 : -1; });
      btns[i].focus();
    };
    btns.forEach((b, i) => {
      b.addEventListener('click', () => {
        if (b.dataset.cmd === 'clear') {
          btns.forEach((x) => { if (x.hasAttribute('aria-pressed')) x.setAttribute('aria-pressed', 'false'); });
        } else if (b.hasAttribute('aria-pressed')) {
          b.setAttribute('aria-pressed', String(b.getAttribute('aria-pressed') !== 'true'));
        }
      });
      b.addEventListener('keydown', (e) => {
        const n = btns.length;
        const next = { ArrowRight: (i + 1) % n, ArrowLeft: (i - 1 + n) % n, Home: 0, End: n - 1 }[e.key];
        if (next === undefined) return;
        e.preventDefault();
        focusAt(next);
      });
    });
  });
})();

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tbg07-bg: #20261c;
  --color-tbg07-bg2: #2c3626;
  --color-tbg07-bar: #f4f7f0;
  --color-tbg07-line: #d8e0ce;
  --color-tbg07-hover: #e5ecdd;
  --color-tbg07-ink: #1a2415;
  --color-tbg07-tip: #eef4e8;
  --color-tbg07-mut: #4a5c3f;
  --color-tbg07-mut: oklch(0.44 0.05 135);
  --color-tbg07-on: #12200a;
  --color-tbg07-acc: #93cf52;
  --color-tbg07-acc: oklch(0.78 0.16 132);
  --color-tbg07-acc-d: #4d8a1c;
  --color-tbg07-acc-d: oklch(0.56 0.14 135);
  --shadow-tbg07-lift: 0 24px 50px -22px rgb(0 0 0 / 0.75), inset 0 0 0 1px rgb(255 255 255 / 0.6);
  --font-tbg07-sans: 'Instrument Sans', system-ui, sans-serif;
  --font-tbg07-serif: 'Newsreader', Georgia, serif;
}
/* Root rule: the full-bleed contract, restated so the stage is sized before the browser build compiles. */

.tbg-07 {
  width: 100%;
  min-height: 100svh;
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400..700&family=Newsreader:ital,opsz,wght@0,6..72,400..700;1,6..72,400..700&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • .btn-07__tool::after{content:attr(data-tip)…} became after:content-[attr(data-tip)] plus positioning utilities; the arrow is a before: border triangle.
  • [aria-pressed=true] became the built-in aria-pressed: variant.
  • UI upgrade: the toggles actually toggle now (the original only drew a pressed state), Clear resets them, and a sample paragraph previews the formatting live.
  • UI upgrade: tooltips show keyboard shortcuts, the toolbar is one Tab stop with arrow/Home/End navigation, and muted icons reach 7:1 on the bar.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Icon Button Group Toolbar with Tooltips (Rich Text Editor)
Source: https://codefronts.com/components/tailwind-css-button-groups/tailwind-icon-button-group-toolbar-tooltips/

A rich-text formatting toolbar: icon buttons with CSS-only tooltips (after:content-[attr(data-tip)]) that also appear on keyboard focus, real toggle state via aria-pressed:, and a sample paragraph that previews bold, italic and underline as you press them. A short controller adds toolbar arrow-key navigation and the toggles themselves.
## HTML
```html
<section class="tbg-07 w-full min-h-svh grid place-items-center bg-tbg07-bg bg-linear-150 from-tbg07-bg to-tbg07-bg2 px-5 py-14 font-tbg07-sans text-tbg07-ink" aria-label="Icon button group toolbar demo">
  <div class="group/ed w-full max-w-[34rem] rounded-2xl bg-tbg07-bar shadow-tbg07-lift">
    <div data-tbg07-bar role="toolbar" aria-label="Text formatting" class="flex flex-wrap items-center gap-0.5 border-b border-tbg07-line p-1.5">
      <button type="button" data-cmd="bold" aria-label="Bold" aria-pressed="true" data-tip="Bold · ⌘B" tabindex="0" class="relative grid size-11 cursor-pointer place-items-center rounded-xl text-tbg07-mut transition-colors hover:bg-tbg07-hover hover:text-tbg07-ink aria-pressed:bg-tbg07-acc aria-pressed:text-tbg07-on focus-visible:z-1 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg07-acc-d before:pointer-events-none before:absolute before:bottom-[calc(100%+4px)] before:left-1/2 before:-translate-x-1/2 before:border-[5px] before:border-transparent before:border-t-tbg07-ink before:opacity-0 before:transition-opacity before:content-[''] hover:before:opacity-100 focus-visible:before:opacity-100 after:pointer-events-none after:absolute after:bottom-[calc(100%+14px)] after:left-1/2 after:-translate-x-1/2 after:rounded-lg after:bg-tbg07-ink after:px-2.5 after:py-1.5 after:text-xs after:font-semibold after:whitespace-nowrap after:text-tbg07-tip after:opacity-0 after:transition-[opacity,translate] after:content-[attr(data-tip)] hover:after:-translate-y-0.5 hover:after:opacity-100 focus-visible:after:opacity-100 motion-reduce:after:transition-none"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linejoin="round" aria-hidden="true"><path d="M7 5h6a3.5 3.5 0 0 1 0 7H7zM7 12h7a3.5 3.5 0 0 1 0 7H7z"/></svg></button>
      <button type="button" data-cmd="italic" aria-label="Italic" aria-pressed="false" data-tip="Italic · ⌘I" tabindex="-1" class="relative grid size-11 cursor-pointer place-items-center rounded-xl text-tbg07-mut transition-colors hover:bg-tbg07-hover hover:text-tbg07-ink aria-pressed:bg-tbg07-acc aria-pressed:text-tbg07-on focus-visible:z-1 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg07-acc-d before:pointer-events-none before:absolute before:bottom-[calc(100%+4px)] before:left-1/2 before:-translate-x-1/2 before:border-[5px] before:border-transparent before:border-t-tbg07-ink before:opacity-0 before:transition-opacity before:content-[''] hover:before:opacity-100 focus-visible:before:opacity-100 after:pointer-events-none after:absolute after:bottom-[calc(100%+14px)] after:left-1/2 after:-translate-x-1/2 after:rounded-lg after:bg-tbg07-ink after:px-2.5 after:py-1.5 after:text-xs after:font-semibold after:whitespace-nowrap after:text-tbg07-tip after:opacity-0 after:transition-[opacity,translate] after:content-[attr(data-tip)] hover:after:-translate-y-0.5 hover:after:opacity-100 focus-visible:after:opacity-100 motion-reduce:after:transition-none"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M15 5h-5M14 19H9M14 5l-4 14"/></svg></button>
      <button type="button" data-cmd="underline" aria-label="Underline" aria-pressed="false" data-tip="Underline · ⌘U" tabindex="-1" class="relative grid size-11 cursor-pointer place-items-center rounded-xl text-tbg07-mut transition-colors hover:bg-tbg07-hover hover:text-tbg07-ink aria-pressed:bg-tbg07-acc aria-pressed:text-tbg07-on focus-visible:z-1 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg07-acc-d before:pointer-events-none before:absolute before:bottom-[calc(100%+4px)] before:left-1/2 before:-translate-x-1/2 before:border-[5px] before:border-transparent before:border-t-tbg07-ink before:opacity-0 before:transition-opacity before:content-[''] hover:before:opacity-100 focus-visible:before:opacity-100 after:pointer-events-none after:absolute after:bottom-[calc(100%+14px)] after:left-1/2 after:-translate-x-1/2 after:rounded-lg after:bg-tbg07-ink after:px-2.5 after:py-1.5 after:text-xs after:font-semibold after:whitespace-nowrap after:text-tbg07-tip after:opacity-0 after:transition-[opacity,translate] after:content-[attr(data-tip)] hover:after:-translate-y-0.5 hover:after:opacity-100 focus-visible:after:opacity-100 motion-reduce:after:transition-none"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M7 4v7a5 5 0 0 0 10 0V4M6 20h12"/></svg></button>
      <span aria-hidden="true" class="mx-1.5 h-6 w-px bg-tbg07-line"></span>
      <button type="button" data-cmd="link" aria-label="Insert link" data-tip="Link · ⌘K" tabindex="-1" class="relative grid size-11 cursor-pointer place-items-center rounded-xl text-tbg07-mut transition-colors hover:bg-tbg07-hover hover:text-tbg07-ink focus-visible:z-1 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg07-acc-d before:pointer-events-none before:absolute before:bottom-[calc(100%+4px)] before:left-1/2 before:-translate-x-1/2 before:border-[5px] before:border-transparent before:border-t-tbg07-ink before:opacity-0 before:transition-opacity before:content-[''] hover:before:opacity-100 focus-visible:before:opacity-100 after:pointer-events-none after:absolute after:bottom-[calc(100%+14px)] after:left-1/2 after:-translate-x-1/2 after:rounded-lg after:bg-tbg07-ink after:px-2.5 after:py-1.5 after:text-xs after:font-semibold after:whitespace-nowrap after:text-tbg07-tip after:opacity-0 after:transition-[opacity,translate] after:content-[attr(data-tip)] hover:after:-translate-y-0.5 hover:after:opacity-100 focus-visible:after:opacity-100 motion-reduce:after:transition-none"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M10 13a4 4 0 0 0 5.7.6l2.6-2.6a4 4 0 0 0-5.6-5.7l-1.5 1.5M14 11a4 4 0 0 0-5.7-.6L5.7 13a4 4 0 0 0 5.6 5.7l1.5-1.5"/></svg></button>
      <button type="button" data-cmd="code" aria-label="Inline code" aria-pressed="false" data-tip="Code · ⌘E" tabindex="-1" class="relative grid size-11 cursor-pointer place-items-center rounded-xl text-tbg07-mut transition-colors hover:bg-tbg07-hover hover:text-tbg07-ink aria-pressed:bg-tbg07-acc aria-pressed:text-tbg07-on focus-visible:z-1 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg07-acc-d before:pointer-events-none before:absolute before:bottom-[calc(100%+4px)] before:left-1/2 before:-translate-x-1/2 before:border-[5px] before:border-transparent before:border-t-tbg07-ink before:opacity-0 before:transition-opacity before:content-[''] hover:before:opacity-100 focus-visible:before:opacity-100 after:pointer-events-none after:absolute after:bottom-[calc(100%+14px)] after:left-1/2 after:-translate-x-1/2 after:rounded-lg after:bg-tbg07-ink after:px-2.5 after:py-1.5 after:text-xs after:font-semibold after:whitespace-nowrap after:text-tbg07-tip after:opacity-0 after:transition-[opacity,translate] after:content-[attr(data-tip)] hover:after:-translate-y-0.5 hover:after:opacity-100 focus-visible:after:opacity-100 motion-reduce:after:transition-none"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 8l-5 4 5 4M15 8l5 4-5 4"/></svg></button>
      <span aria-hidden="true" class="mx-1.5 h-6 w-px bg-tbg07-line"></span>
      <button type="button" data-cmd="clear" aria-label="Clear formatting" data-tip="Clear formatting" tabindex="-1" class="relative grid size-11 cursor-pointer place-items-center rounded-xl text-tbg07-mut transition-colors hover:bg-tbg07-hover hover:text-tbg07-ink focus-visible:z-1 focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-tbg07-acc-d before:pointer-events-none before:absolute before:bottom-[calc(100%+4px)] before:left-1/2 before:-translate-x-1/2 before:border-[5px] before:border-transparent before:border-t-tbg07-ink before:opacity-0 before:transition-opacity before:content-[''] hover:before:opacity-100 focus-visible:before:opacity-100 after:pointer-events-none after:absolute after:bottom-[calc(100%+14px)] after:left-1/2 after:-translate-x-1/2 after:rounded-lg after:bg-tbg07-ink after:px-2.5 after:py-1.5 after:text-xs after:font-semibold after:whitespace-nowrap after:text-tbg07-tip after:opacity-0 after:transition-[opacity,translate] after:content-[attr(data-tip)] hover:after:-translate-y-0.5 hover:after:opacity-100 focus-visible:after:opacity-100 motion-reduce:after:transition-none"><svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 5h12M10 5L8 19M15 5l-1 5M4 20L20 4"/></svg></button>
    </div>
    <p class="px-6 pt-5 pb-6 font-tbg07-serif text-[19px] leading-relaxed text-tbg07-ink group-has-[[data-cmd=bold][aria-pressed=true]]/ed:font-semibold group-has-[[data-cmd=italic][aria-pressed=true]]/ed:italic group-has-[[data-cmd=underline][aria-pressed=true]]/ed:underline group-has-[[data-cmd=underline][aria-pressed=true]]/ed:decoration-tbg07-acc-d group-has-[[data-cmd=underline][aria-pressed=true]]/ed:underline-offset-4 group-has-[[data-cmd=code][aria-pressed=true]]/ed:font-mono group-has-[[data-cmd=code][aria-pressed=true]]/ed:text-[16px]">Good tools disappear. You notice the work, not the buttons you pressed to make it.</p>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tbg07-bg: #20261c;
  --color-tbg07-bg2: #2c3626;
  --color-tbg07-bar: #f4f7f0;
  --color-tbg07-line: #d8e0ce;
  --color-tbg07-hover: #e5ecdd;
  --color-tbg07-ink: #1a2415;
  --color-tbg07-tip: #eef4e8;
  --color-tbg07-mut: #4a5c3f;
  --color-tbg07-mut: oklch(0.44 0.05 135);
  --color-tbg07-on: #12200a;
  --color-tbg07-acc: #93cf52;
  --color-tbg07-acc: oklch(0.78 0.16 132);
  --color-tbg07-acc-d: #4d8a1c;
  --color-tbg07-acc-d: oklch(0.56 0.14 135);
  --shadow-tbg07-lift: 0 24px 50px -22px rgb(0 0 0 / 0.75), inset 0 0 0 1px rgb(255 255 255 / 0.6);
  --font-tbg07-sans: 'Instrument Sans', system-ui, sans-serif;
  --font-tbg07-serif: 'Newsreader', Georgia, serif;
}
/* Root rule: the full-bleed contract, restated so the stage is sized before the browser build compiles. */

.tbg-07 {
  width: 100%;
  min-height: 100svh;
}
```

## JavaScript
```js
(() => {
  document.querySelectorAll('[data-tbg07-bar]').forEach((bar) => {
    if (bar.dataset.wired) return;
    bar.dataset.wired = '1';
    const btns = [...bar.querySelectorAll('button')];
    const focusAt = (i) => {
      btns.forEach((b, j) => { b.tabIndex = j === i ? 0 : -1; });
      btns[i].focus();
    };
    btns.forEach((b, i) => {
      b.addEventListener('click', () => {
        if (b.dataset.cmd === 'clear') {
          btns.forEach((x) => { if (x.hasAttribute('aria-pressed')) x.setAttribute('aria-pressed', 'false'); });
        } else if (b.hasAttribute('aria-pressed')) {
          b.setAttribute('aria-pressed', String(b.getAttribute('aria-pressed') !== 'true'));
        }
      });
      b.addEventListener('keydown', (e) => {
        const n = btns.length;
        const next = { ArrowRight: (i + 1) % n, ArrowLeft: (i - 1 + n) % n, Home: 0, End: n - 1 }[e.key];
        if (next === undefined) return;
        e.preventDefault();
        focusAt(next);
      });
    });
  });
})();
```

How this works

Tooltips from the attribute. Each button carries its label once, in data-tip; two pseudo-elements draw the bubble and its arrow:

<button data-tip="Bold · ⌘B" aria-label="Bold" aria-pressed="true"
  class="after:content-[attr(data-tip)] after:opacity-0
         hover:after:opacity-100 focus-visible:after:opacity-100
         aria-pressed:bg-tbg07-acc">

The preview listens with :has(). The editor card is group/ed; the sample text asks whether a pressed bold button exists and bolds itself. The script only ever flips aria-pressed.

Make it yours

  • Tooltip below: swap after:bottom-[calc(100%+10px)] for after:top-[calc(100%+10px)] and flip the arrow.
  • Add a button: copy one, set data-cmd, data-tip and aria-label; toggles need aria-pressed="false".
  • Light toolbar on dark: tokens --color-tbg07-bar and --color-tbg07-ink.

Gotchas — read before shipping

  • Generated tooltip text is not reliably announced — keep aria-label on every icon button.
  • Action buttons (Link, Clear) must not carry aria-pressed, or they read as toggles.
  • Tooltips are clipped by overflow-hidden ancestors; leave room above the bar.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

attr() in content is universal; Tailwind v4 sets the floor.

Techniques used in this demo

Utilitiesfirst-class utility or variant

Search CodeFronts

Loading…