
Pure CSS Floating Modal Close Button
Published
28 hand-coded CSS close (X) buttons you can copy-paste into any project — pure CSS floating modal close, WCAG focus-ring accessible close, iOS-style circular tab close, chat window close (Slack/Discord/Intercom pattern), cookie consent dismiss (GDPR/CCPA-compliant), IAB mobile ad banner close (44px tap target), neumorphic soft shadow, hamburger-to-X morph, video player overlay close, native <dialog> drawer close, plus 18 aesthetic variants (magnetic cross, liquid pop, glitch, origami crease, particle burst, hold-to-confirm, toast dismiss, brutalist stamp, galaxy vortex, and more). Every button is a real <button aria-label> element (never a <div>), meets WCAG 2.5.8 minimum target size (24×24px absolute, 44×44px mobile), and has a WCAG 2.4.7 :focus-visible indicator that never depends on color alone. The X glyph is CSS-drawn (rotated ::before/::after bars or masked strokes) so it never depends on the "✕" character rendering across fonts, and always aria-hidden. Compositor-only motion (transform + opacity, never width/left/top), prefers-reduced-motion guarded, oklch() palettes, @property-animated angles, conic-gradient progress rings, backdrop-filter overlays. Scoped under .ccb-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.

Published

Published

Published

Published

Published

Published

Published

Published

Published

Published

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated

PublishedUpdated
Tweak the exact look in our visual generators — no signup, instant copy-paste.
<button type="button" aria-label="Close"> — not a <div> or bare <span>. The button element is keyboard-focusable, fires on Enter AND Space, and screen readers announce it as a button by role. The X glyph itself should be decorative (a pseudo-element or an aria-hidden="true" icon) so assistive tech reads 'Close' — not the character. Every one of the 28 demos in this collection ships this exact pattern. Four quality axes graded on every close button in this collection: (1) SEMANTIC HTML — real <button> with an accessible name (either aria-label or visible text). The X drawn with CSS never relies on the '✕' character rendering (which varies by font). (2) WCAG COMPLIANCE — every button meets WCAG 2.5.8 minimum target size (24×24px absolute minimum, 44×44px mobile ideal via padding or pseudo-element hit areas), and every one has a WCAG 2.4.7 :focus-visible indicator that never depends on color alone. (3) COMPOSITOR-ONLY MOTION — transform + opacity keyframes only, never width/left/top (which trigger layout thrash). GPU-friendly, INP-safe. (4) MODERN CSS — oklch() palettes, color-mix() tints, :focus-visible, conic-gradient progress rings, @property-typed angle animation, clip-path, mask-image, backdrop-filter, cubic-bezier springs — each demo teaches at least one contemporary technique beyond the X itself.position: relative, then add ::before and ::after as thin bars (typically width: 2px; height: 60%) absolutely centered, and rotate one 45deg and the other -45deg. This gives a crisp, scalable X with zero dependencies — no SVG, no icon font, no background image, and it scales perfectly with CSS transforms. Every one of the 28 demos in this collection uses this pattern (or a variant like mask-image for animated stroke effects on Demos 06, 08, 20). The visible '✕' Unicode character is a bad choice because its shape and vertical alignment vary wildly across system fonts — some render it as a bold serif, others as a thin sans-serif, iOS renders it as a colored emoji on some devices. CSS-drawn X gives you pixel-perfect control across every browser and every font stack.<button aria-label="Dismiss cookie banner"> so screen readers announce 'Dismiss cookie banner, button.' Additional context: Demo 05 also shows the correct dismissal flow — the banner slides out on dismiss, then a small 'Show banner again' link appears so users who dismissed accidentally can restore it (required for accessibility compliance where the user hasn't consented but also hasn't rejected — you can't just silently accept). Combines with your cookie-management library (Cookiebot, OneTrust, Osano) to fire the actual consent event.aria-label="Close advertisement" so screen-reader users on mobile can dismiss ads without accidentally clicking through. This is table-stakes for Google Ads publishers — non-compliance costs revenue in Google's ad-quality auction (lower quality score = lower CPM).<span> elements or one span + ::before/::after). On the 'open' state (usually toggled by a <input type="checkbox"> + :checked sibling combinator for pure-CSS, or a JS class toggle), three transforms happen simultaneously: (1) the TOP bar translates down to the vertical center AND rotates 45°. (2) the BOTTOM bar translates up to the vertical center AND rotates -45°. (3) the MIDDLE bar shrinks to opacity: 0 OR scale-x: 0. The result is a smooth morph from hamburger (three horizontal bars) to X (two crossed diagonal bars). Use transform-origin: center on each bar so rotations pivot around the center point. Duration: 200-300ms with an ease-in-out timing function reads clean; longer and the effect feels sluggish. This is the exact pattern shipped by every mobile navigation on the modern web — Vercel, Linear, Stripe, Notion, Framer all use variants. Combine with @media (prefers-reduced-motion: reduce) to disable the morph and swap between hamburger and X instantly for users who've opted out.<dialog> element (Baseline widely-available since 2022 across Chrome/Safari/Firefox) is the modern replacement for role='dialog' + aria-modal + focus-trap JS. Open via dialog.showModal(), close via dialog.close(), and the browser handles focus trapping, Escape-to-close, backdrop click detection (via ::backdrop pseudo-element), inert-ing background content, and returning focus to the trigger on close — all for free. The close button pattern: a <button type="button" aria-label="Close"> in the dialog's top-right, with a click handler that calls dialog.close(). Because the dialog has an implicit form context, you can also use <button type="submit" formmethod="dialog"> — clicking it closes the dialog and returns the button's value via dialog.returnValue. Compared to older patterns: no focus-trap library needed (headlessui, radix-ui, react-modal all handle this via JS — the platform does it natively). No role="dialog" aria-modal="true" attributes needed (implicit on <dialog>). Escape-to-close is automatic. The one gotcha: <dialog> doesn't animate open/close by default; use @starting-style (Chrome 117+, Safari 17.5+, Firefox 129+) or a transition on display via transition-behavior: allow-discrete for smooth open/close animations.<button type="button"> not <div onclick>. Button elements are keyboard-focusable by default, fire on Enter AND Space, and screen readers announce the role. (2) ACCESSIBLE NAME — either aria-label="Close" (when the button contains only an X glyph) OR visible text inside the button (like Demo 05's 'Dismiss' text). Never both (the aria-label overrides visible text and confuses users who see one word but hear another). (3) FOCUS-VISIBLE INDICATOR — a :focus-visible outline (or box-shadow ring) that appears ONLY when the user tabs to the button via keyboard, not on mouse click. The :focus-visible pseudo-class distinguishes keyboard focus from mouse focus, so mouse users don't see a ring on click (visual noise) but keyboard users get a strong indicator. Never use outline: none without providing an alternative — that's WCAG 2.4.7 failure and the #1 accessibility bug on the web. For modal + drawer close patterns, ALSO handle Escape key: the modal itself listens for keydown with event.key === 'Escape' and fires the same close action as the button. Native <dialog> (Demo 10) handles Escape automatically.@property --hold registers a typed custom property that animates smoothly from 0 to 100; on pointerdown the JS starts the animation via btn.style.setProperty('--hold', '100') with a 1.2s transition; on pointerup before completion, resets to 0; on transitionend fires the actual close. Baseline requirements: @property (Chrome 85+, Safari 16.4+, Firefox 128+). Advantages vs 'Are you sure?' modal confirmation: no context switch, no new focus target, faster for power users, harder to trigger by accident (a single accidental click never fires). Used by GitHub (repository deletion), Vercel (project deletion), Stripe (subscription cancellation) — all high-stakes destructive UI. Combine with an aria-live="polite" region that announces progress ('Hold to delete, 50 percent complete') for screen-reader users who can't see the visual progress ring.<script> tag (Astro). React specifics: change class="..." to className="..."; wrap JS in useEffect(() => {...}, []). Next.js App Router: works in Client Components ('use client' directive) — the interactive demos need the browser. Svelte / SvelteKit: use bind:this instead of getElementById. Astro: works out of the box with a <script> tag. Compared to component libraries: shadcn's <DialogClose /> is essentially our Demo 10 pattern with Radix wrapping (adds ~4KB gzipped for the radix-ui/react-dialog dep). Radix Dialog + our pure-CSS close X = same accessibility, zero dependency footprint. Headless UI's close button is our Demo 02 pattern with a controlled open prop. Every demo scoped .ccb-NN so you can drop multiple demos into the same project without collision — the class prefix is your isolation.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.