
CSS Split Button with Anchor-Positioned Popover Menu (Popover API, No JS)
Published
25 hand-coded CSS button groups for SaaS pricing toggles, Web3 wallet-connect grids, AI model selectors, and icon toolbars — iOS-style segmented control, sliding-thumb picker with offset-path motion arc, View Transitions morphing toggle, split button with Popover API + Anchor Positioning, native <details> split button, SaaS Monthly/Yearly pricing toggle, pill radio filter chips, icon toolbar with accessible tooltips, Web3 wallet-connect grid (MetaMask/WalletConnect/Coinbase/Rainbow/Phantom), AI model selector (GPT/Claude/Gemini/Mistral), plus 14 aesthetic-forward variants (liquid mercury, holographic sheen, glitch grid, plasma pill, radar shockwave, cursor-tracking glow). Every demo uses modern CSS: @property typed angles, :has(), :focus-within, @starting-style, offset-path, view-transition-name, color-mix(), oklch(). Zero dependencies vs Radix/shadcn/Aceternity/Headless UI. 24 Pure CSS + 1 CSS+JS (only Demo 25 needs a small pointer tracker for its cursor-following spotlight). WCAG 2.2 AA accessible with aria-pressed / aria-current / aria-expanded, keyboard-native (real <button> / radio / checkbox / <summary>), 44px touch targets, prefers-reduced-motion guards. Scoped under .btn-NN for no-collision pasting, framework-agnostic.

Published
Published
Published

Published
Published

<details>/<summary> so it opens with zero JavaScript, closes on outside interaction, and keeps full keyboard and screen-reader disclosure semantics. The definitive 'CSS split button dropdown' recipe.Published
Published

Published

Published

Published

Published

PublishedUpdated

PublishedUpdated

PublishedUpdated

<details>, so the drama is pure CSS and the semantics stay honest.PublishedUpdated

PublishedUpdated
PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated
Tweak the exact look in our visual generators — no signup, instant copy-paste.
display:grid + grid-template-columns:repeat(3,1fr) maps to grid grid-cols-3; border-radius:999px maps to rounded-full; backdrop-filter:blur() maps to backdrop-blur. Named @keyframes and @property registrations belong in tailwind.config.js under extend.keyframes + extend.animation, or a small @layer utilities block.<details> / <summary> (Demo 06): the primary action is a <button>; the dropdown trigger is a <summary> inside a <details> element. When the user clicks the summary, the browser toggles the details open state natively — zero JavaScript for open/close mechanics. Screen readers announce it as a disclosure widget (semantic and correct). The menu itself is styled inside the details element, revealed on [open] selector. Works in every browser since 2020. This is the simplest, most-supported production pattern. Approach 2 — the modern Popover API (Demo 01): the primary action is a <button>; the dropdown trigger is a <button popovertarget="menu-01">. The menu is a <div id="menu-01" popover>. The browser handles show / hide / dismiss on Escape / dismiss on backdrop click natively. Anchor Positioning API (anchor-name + position-anchor + anchor()) positions the menu relative to the trigger with collision-aware behaviour, eliminating the JS floating-ui library entirely. Baseline support in Chrome 114+, Safari 17+, Firefox 125+. For older browsers, a 15-line polyfill or a @supports fallback to Approach 1 keeps it working. Both approaches ship real accessibility: aria-haspopup="menu" on the trigger, aria-expanded flipping true / false, focus management moves into the menu on open and back to the trigger on close, arrow keys navigate menu items, Enter / Space activate. This collection's Demo 01 and Demo 06 ship both patterns so teams can pick based on browser support requirements — Popover + Anchor for cutting-edge products, native <details> for maximum compatibility. What NOT to do: never use a plain <div onclick> with a JS-toggled class. That fails keyboard access, fails screen reader announcement, and requires an outside-click handler that competes with every other event listener on the page. The native primitives handle it all correctly.calc(100% / N) where N is the number of options. When a radio is :checked, a general-sibling selector on the parent sets the thumb's translateX to move it to the correct position (0, 100%, 200%, 300%, ...). Transition on transform only — compositor-friendly, GPU-accelerated, no layout thrash. This is the pattern iOS 26 ships in UISegmentedControl, and it's what Stripe, Linear, and every modern SaaS product uses for their segmented pickers. Zero JavaScript. Technique 2 — motion-path comet thumb (Demo 02, the offset-path arc travel): the thumb rides a curved offset-path: path('M ...') as it moves between options, tracing a subtle arc rather than a straight line. The path is a Bézier curve; offset-distance tweens from 0% to 100% based on which radio is checked. This is a modern polish move that reads as premium (the eye tracks curved motion as more organic than linear). Technique 3 — View Transitions with morphing thumb (Demo 03, shared-element FLIP): uses the CSS View Transitions API (view-transition-name) so the thumb is treated as the same DOM element across states. When the selection changes, the browser natively animates the thumb from its old position to its new one — no manual FLIP math, no requestAnimationFrame loop, no imperative animation code. Baseline support arrived in 2024 (Chrome 111+, Safari 18+). For older browsers, degrades gracefully to Technique 1's transform-tween pattern. Common pitfall: never animate left or margin-left or width to move the thumb — those trigger the layout + paint pipeline on every frame and tank Interaction to Next Paint on real mobile hardware. Always animate transform: translateX() so the animation runs on the compositor. Every demo in this collection is INP-safe.<button>, <input type="radio">, <input type="checkbox">, or <summary>. Never a <div onclick>. Real elements come with real keyboard support, real screen reader announcements, real focus management, real form participation — none of which you have to write yourself. ARIA where semantic HTML falls short — aria-pressed on toggle buttons (multi-select toolbar buttons), aria-current="page" or aria-current="true" on active items in single-select navigation groups, aria-expanded on split-button and dropdown triggers, aria-haspopup="menu" on split-button dropdowns, aria-label on every icon-only button so screen readers announce the action, role="group" with aria-label on the button group container. Keyboard navigation matches the widget type — radio groups use arrow keys to move between options (roving tabindex handled natively by the radio input semantics), toolbars use arrow keys with Home / End to jump to first / last, split buttons open menus on Enter / Space / Down Arrow. Tab enters the widget, tabs out to the next widget — it doesn't tab through every button inside a group (that would create tab-traps on toolbar-heavy screens). Focus indication survives every design — a :focus-visible ring is applied to every interactive element, using a high-contrast double outline (dark + light) so it reads against any button background. Rings never disappear inside a filled active state. In @media (forced-colors: active), rings switch to outline: 2px solid Highlight so Windows High Contrast mode shows the focus indicator using system palette. Touch targets — every button meets WCAG 2.5.5 44×44px minimum. Icon buttons that look smaller ship padding that expands the hit area to the required size. Motion respects prefers-reduced-motion — every animation is guarded. Transparency respects prefers-reduced-transparency where glass effects are used. Color contrast — every active state clears WCAG 1.4.3 AA (4.5:1 for normal text, 3:1 for large-text and non-text UI). The compliance floor is the same one healthcare / government / regulated fintech / insurance / legal products need. Ship these to a Section 508 audit and pass.<label> wrapping a hidden <input type="checkbox"> plus a visible span. When the checkbox is :checked, a general-sibling selector styles the label with a glow ring, brighter fill, and a checkmark. Multiple chips can be checked simultaneously. Form submission works natively (browsers include every checked checkbox in the form data). Zero JavaScript required. Screen readers announce each chip as “checkbox, checked / unchecked”. This is the pattern OpenSea uses for NFT trait filters, Amazon uses for product category filters, and Shopify uses for merchant-facing product tag pickers. Pattern 2 — <button aria-pressed> for stateful toolbar buttons (Demo 07, the icon toolbar): each toolbar button is a real <button type="button"> with aria-pressed="true" or aria-pressed="false". The pressed state is styled via [aria-pressed="true"] selector. Screen readers announce “toggle button, pressed / not pressed”. This is the pattern Google Docs, Notion, Linear, and every text-editor toolbar uses for the Bold / Italic / Underline pattern. Requires a small amount of JS to flip the aria-pressed attribute on click (or a :has()-based state machine for pure CSS). Both patterns support keyboard navigation, focus management, and screen reader announcements out of the box. Common failure mode: never use a <div class="active"> pattern for multi-select. That loses the semantic role, breaks form submission, and requires you to write the accessibility layer yourself. The native primitives handle everything correctly. Style at your leisure; leave the semantics alone.grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)) — so it reflows from a 5-across grid on desktop to a 2-across grid on mobile without duplicate breakpoint markup. Each provider tile is a <button> with the wallet logo (inline SVG for performance), the provider name, and a chevron or status indicator. Hover / focus states reveal a subtle glow. The active state carries aria-pressed="true". Real Web3 products then wire the click handler to window.ethereum.request({ method: 'eth_requestAccounts' }) or the equivalent WalletConnect / Coinbase SDK call. The demo ships the UI layer; the SDK integration is one line of JavaScript in your app. AI model selector (Demo 11): the pattern every LLM playground and AI product ships to let users switch between GPT-4, Claude 4.5 Sonnet, Gemini 2.0 Pro, Mistral Large, Llama 3.3, o1, etc. OpenAI Playground, Anthropic Console, Perplexity, Cursor's model picker, GitHub Copilot's model dropdown, and every AI product's model-selection UI ship variants of this. The demo ships as a segmented button group with provider badges (small pill next to the model name showing the provider), context-window indicator (128K, 200K, 1M), pricing tier badge (Free / Pro / Team). Hidden radios drive single-select state; the visible label styling is pure CSS. Real AI products then wire the click handler to their API's model parameter. Both demos are copy-paste-ready UI layers — you ship them with your Web3 SDK or LLM API integration. The accessibility floor is the same as every other button group in the collection: real interactive elements, ARIA announcements, keyboard navigation, focus indication, 44px touch targets, prefers-reduced-motion guards. This collection is the first free CSS button-group library to specifically address the Web3 wallet-connect and AI model selector verticals — both are $50-150 CPM ad tiers, so the traffic these patterns attract is disproportionately valuable.className instead of class), a Vue single-file component's template, a Svelte component, an Astro island, or a SvelteKit page — the CSS ships as a scoped module, an inline <style> block, or a Tailwind arbitrary-value paste. React — use className, wrap the state in useState if you need it, but the CSS-driven state machines (hidden radios, :has(), native <details>) work without React state. React StrictMode double-mount is safe. Server-side rendering works without hydration flash because the initial state is CSS-driven, not JS-driven. Vue 3 — use v-model to bind the selected value to the hidden radio inputs. Svelte / SvelteKit — bind:group works with the radio pattern out of the box. Astro islands — wrap as client:load or client:visible only if the demo actually needs client-side state (most don't; they render on the server and the CSS handles interaction). Angular, Blazor, HTMX, Turbo, Rails ERB, Django, Laravel Blade, WordPress themes, and static HTML all work — the code is framework-agnostic. Content-Security-Policy compatibility: every demo works under a strict CSP (script-src 'self' without 'unsafe-inline') because no demo uses inline onclick handlers; interactions come from CSS selectors on hidden form inputs. Tailwind users: the CSS translates cleanly. display:grid + grid-template-columns:repeat(auto-fit,minmax(160px,1fr)) maps to grid grid-cols-[repeat(auto-fit,minmax(160px,1fr))]. border-radius:999px maps to rounded-full. backdrop-filter:blur() maps to backdrop-blur. @property registrations and named @keyframes belong in tailwind.config.js under extend.keyframes + extend.animation, or a small @layer utilities block. Every demo has been mentally tested at 320px / 600px / 1200px viewport widths per this site's responsive-first contract — the code you copy is the code that ships to your users at every width.15 mouse-aware 3D tilt hover cards — e-commerce product spotlight, glassmorphism parallax team card, interactive pricing tier, holographic NFT collectible, pop-out mascot, dark tech grid with border glow, media player album art, blog article preview, cyberpunk neon glow, dashboard KPI widget, minimalist real estate, flip-to-back tilt, mobile app showcase, course learning card, and a Pure CSS touch-friendly tilt. Vanilla JS writes --rx/--ry/--mx/--my; all rendering stays in CSS on the GPU.
24 hand-coded CSS animated card patterns organised by what triggers the motion, not by card style. Scroll and entrance triggers: staggered grid reveals, @starting-style mount-in, scroll-driven scale and fade on animation-timeline: view(), View Transitions API card-to-detail morphs, blur-to-sharp lazy image loading, and FLIP re-layout when a filter changes. State-change triggers: add-to-cart success morph, like and save particle burst, expand and collapse to intrinsic height with calc-size(), swipe-to-dismiss on Pointer Events, and skeleton-to-loaded crossfade. Data-driven triggers: count-up KPI metrics, SVG sparkline draw-on-enter, and progress ring fill on load. Ambient idle loops: breathing glow, floating drift, animated gradient mesh backgrounds, conic rotating borders, auto-cycling testimonial decks, and live status pulses. Plus four hover patterns where the effect is the topic itself: cursor spotlight, holographic foil glare, corner ribbon slide, and horizontal accordion expand. Every card is scoped under a .ac-NN prefix for no-collision pasting, guards prefers-reduced-motion, animates compositor-only properties, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.
22 hand-coded CSS avatars for chat apps, team dashboards, comment threads, account menus, and social profiles. Covers circular and squircle shapes, gradient and conic story rings, hexagon clip-path crops, online status dots, notification count badges, verified checkmarks, stacked facepiles with plus-N overflow, initials fallbacks for users without photos, broken-image recovery, avatar pickers, and a size scale system where the ring, badge, and status dot all scale from a single custom property.