Skip to content

feat: decouple from Tailwind + CSS-variable theming + sliding active indicator - #5

Merged
NagyVikt merged 1 commit into
mainfrom
claude/tailwind-decouple-indicator
Jun 22, 2026
Merged

feat: decouple from Tailwind + CSS-variable theming + sliding active indicator#5
NagyVikt merged 1 commit into
mainfrom
claude/tailwind-decouple-indicator

Conversation

@NagyVikt

Copy link
Copy Markdown
Contributor

Final follow-up PR — the styling overhaul (#3 Tailwind decouple, #6 theming, #8 sliding indicator).

Why

The bar styled itself entirely with Tailwind utility classes, so any consumer without Tailwind — or who forgot to add the package to their content globs — got a completely unstyled bar. This was the biggest adoption risk, and a prerequisite for real theming.

What changed

No Tailwind required (#3)

  • New src/styles.ts ships a self-contained stylesheet under an mtb- class namespace. The component injects it once per document at runtime (useInsertionEffect, SSR-safe, shared by all instances).
  • Tailwind variants are replicated in plain CSS: lg:hidden@media (min-width:1024px), motion-reduce:prefers-reduced-motion, hover:/active: → pseudo-states.
  • For SSR/no-FOUC: tsup emits dist/styles.css (via onSuccess), added as a "./styles.css" export, plus an exported STYLE_CSS string.

CSS-variable theming (#6)
Every themable color/size reads var(--mtb-*, <default>), so consumers rebrand by setting variables on any ancestor — no fork. Full table in the README (--mtb-accent, --mtb-surface, --mtb-radius, …).

Sliding active indicator (#8)
Replaces the per-tab active background with one pill that animates between tabs. Position is measured via offsetLeft/Top (layout-correct in LTR and RTL), recomputed on active/compact/dir/size changes (incl. ResizeObserver), reduced-motion aware, and hidden when no tab is active.

Playground
Dropped the Tailwind CDN, sized icons with a single plain-CSS rule, and added a live accent-theming button — demonstrating the bar renders fully styled with zero Tailwind.

Tests

  • Existing semantic tests (roles/aria/dir/badges) unchanged and still green — they never depended on class names.
  • Added: stylesheet injected once & shared; indicator renders and shows/hides with the active tab.
  • 15 tests pass.

Verification

npm run type-check, npm run build (emits dist/styles.css, .d.ts 4.36KB), npm test all green. vite build playground builds clean. The sliding-indicator geometry is exercised in the playground (jsdom has no layout, so geometry isn't unit-tested).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WCE1RpZpzupe5kvo5aGGvV


Generated by Claude Code

…dicator

The bar previously styled itself entirely with Tailwind utility classes, so any
consumer without Tailwind (or who forgot to add the package to their content
globs) got an unstyled bar — the biggest adoption risk.

- Ship a self-contained stylesheet (src/styles.ts) using an `mtb-` class
  namespace; the component injects it once per document at runtime via
  useInsertionEffect (SSR-safe, shared across instances). No Tailwind needed.
- Theme via CSS variables (--mtb-accent, --mtb-surface, …) with the current
  design as inline fallbacks, so consumers rebrand by setting vars on any
  ancestor. Also emit dist/styles.css (tsup onSuccess) + a "./styles.css"
  export, and export STYLE_CSS, for SSR/no-FOUC.
- Replace the per-tab active background with a single sliding indicator pill
  that animates between tabs (measured via offsetLeft/Top, layout-correct in
  LTR and RTL, recomputed on active/compact/dir/size changes, reduced-motion
  aware, hidden when no tab is active).
- Replicate the Tailwind variants in plain CSS: lg:hidden -> @media
  (min-width:1024px), motion-reduce -> prefers-reduced-motion, hover/active
  pseudo-states.
- Playground: drop the Tailwind CDN, size icons with one plain CSS rule, add a
  live accent-theming control — proving the bar renders with zero Tailwind.
- Tests for stylesheet injection and the indicator; README "Styling & theming"
  section (variables table + SSR note).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCE1RpZpzupe5kvo5aGGvV
@NagyVikt
NagyVikt marked this pull request as ready for review June 22, 2026 10:44
@NagyVikt
NagyVikt merged commit 59de923 into main Jun 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants