01. CSS Submit Button (Form Primary) (opens in a new tab)
outline plus a soft halo so it survives on any background. Zero JavaScript, four states, one token block.51 copy-paste CSS buttons for submit forms, primary and secondary CTAs, ghost and outline variants, icon-only with accessible tooltips, gradient CTAs, loading spinners, disabled references, button groups, state machines (add-to-cart, download, play/pause), hover effects, click animations (ripple, glitch, ink spread), and material studies (brass, walnut, aged copper with @property patina). Scoped under .cb-NN, prefers-reduced-motion guarded, framework-agnostic.
Related20 CSS Loading Buttons36 CSS Button Hover Effects20 CSS Animated Buttons20 CSS SVG Stroke Buttons
Course pickPartner: Zero to Mastery
Affiliate linkWant to build production-ready CSS layouts, animations and UI from scratch?
You just browsed 51 buttons. Learn the Flexbox, Grid and animation thinking behind them, so the next component starts in your head, not in a copy-paste.
Every ZTM course in one membership: $25/month, paid yearly at $299 · 30‑day money-back guarantee
The CSS Bootcamp. We may earn a commission, at no extra cost to you.
outline plus a soft halo so it survives on any background. Zero JavaScript, four states, one token block.background-size transition on a gradient — no pseudo-element, no overflow clipping, and the border is drawn in currentColor so a single colour change re-skins everything.@starting-style and is positioned with CSS anchor positioning, falling back to a plain absolute offset.@property registers the angle as an <angle> type — plain custom properties cannot be animated.stroke-dashoffset, and the state lives in a single data-state attribute mirrored to aria-busy and a live region.disabled, and aria-disabled (focusable, explains itself, refuses activation). The hover/focus/pressed cards are frozen with state classes so you can compare all six simultaneously instead of chasing a pointer.:has(), and the active label crosses over the thumb by inverting colour at the same moment.role="progressbar" so the value is exposed, not implied.clip-path: polygon() with the same point count, so the browser interpolates every vertex; a progress ring sits around it and the whole thing is a single checkbox.clip-path and pushed apart in cyan and magenta. Steps-based timing keeps it digital — no easing, no blur, no JavaScript.text-shadow and box-shadow rather than a blur filter, so it stays crisp at any zoom, and the plasma is a registered-angle conic gradient under a blur.animationend so the DOM never grows.backdrop-filter blurs and desaturates whatever is behind it, a bright inner rim catches the light on two edges only, frost bloom collects in the corners, and pressing sends a hairline crack across the face.preserve-3d, backface-visibility — and a checkbox holding the state, so no JavaScript is involved.aria-live region, so the message is announced rather than merely displayed. Press again to reset.clip-path, and a press that drops the slab into its own shadow with a puff of dust at the base.mask-composite, an inner top-light, a spring lift on hover, a shortcut chip that dims as the arrow slides, and a focus ring that respects the pill's radius exactly.clip-path, near-black translucency over a blurred backdrop, two facet planes shaded by overlapping gradients, and a razor of light that runs along the leading edge on hover.:has(:checked) 3D rotation.filter transition, exactly how the chemistry actually behaves. The frame tilts, lifts off the table, and the caption is hand-written on the wide bottom border.tabular-nums so nothing shifts as they change.:has() drives the rotation.aria-pressed, not just its look.counter-reset and printed through content. Zigzag torn bottom edge, faded thermal ink, monospaced columns.Tweak the exact look in our visual generators — no signup, instant copy-paste.
button element with type='submit' inside a form element — never a div or an a with a click handler. Style it with padding, a solid background or gradient, border-radius, font-weight, and cursor pointer. Add :hover and :active states with a transition so the change is smooth, and a :focus-visible outline so keyboard users can see focus. Set a minimum touch target of 44×44px so mobile taps hit reliably. Demo 01 in this collection (CSS Submit Button (Form Primary)) ships the full recipe including aria-busy handling for the loading state — open the code panel to copy.aria-label on the button describing the action (aria-label='Delete file'). Second, use the native Popover API for the tooltip: put popover on a sibling element and popovertarget on the button — the browser handles focus, ESC-to-dismiss, and light-dismiss for free. Third, link them with aria-describedby so screen readers announce both the label AND the tooltip content. Demo 04 (Icon-Only Button with Accessible Tooltip) ships this pattern with a CSS-anchor-positioning fallback for browsers without Popover support.data-state attribute: idle → loading → success → reset. Each state is a CSS rule that shows or hides layered spans inside the button — label, spinner, checkmark. On click, toggle data-state='loading' and set aria-busy='true', then after the async work completes swap to data-state='success' and aria-busy='false'. Common pitfalls: use aria-disabled='true' instead of the disabled attribute so the button stays focusable during loading (users can tab away), announce the state to screen readers via a polite live region, and add pointer-events: none to prevent double-clicks. Demo 06 (Loading Spinner Button) ships the full recipe in 18 lines of JS.<input type='radio'> elements inside a fieldset with role='group' on a wrapper — this gives you keyboard arrow-key navigation for free, exclusive selection, and screen-reader semantics. Visually hide the radios with a clip-path pattern (not display: none which removes them from the tab order), then style the sibling <label> elements as segments. Use :has() on the wrapper to style the group when any radio is checked, with an @supports not selector(:has(*)) fallback for older browsers. Demo 08 (CSS Button Group Component) ships the pattern with arrow-key nav and dark-mode support.disabled attribute grays out the button AND removes it from the tab order — use for form validation errors where the disabled state is temporary. aria-disabled='true' visually disables but keeps focus and Space/Enter — pair with pointer-events: none and a JS handler that ignores clicks, use for pending async operations where users need to tab past. Style both with opacity: 0.5 and cursor: not-allowed, and never disable the ONLY action on a screen (offer a why-disabled tooltip instead). Demo 07 (Disabled Button State Full Reference) shows every state side-by-side: default, hover, focus, active, disabled, loading.bg-*, hover:*, focus:ring-* — great for design-system patterns, but the utility system doesn't extend to conic-ring hold-to-confirm, gooey SVG filters, magnetic cursor-follow, mercury ripples, or 31 material studies (brass letterpress, frosted ice, velvet, IBM punch card). For those you write custom CSS anyway. shadcn/ui and Material UI ship pre-styled button components with variant slots — excellent for design systems, but they don't include the material studies or click-effect studies in this collection. The 51 demos here ship raw so you can lift a technique and drop it into React, Vue, Svelte, Astro, or plain HTML with zero adaptation. Copy the CSS, wrap the interaction in your framework's event handlers, done.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.