01. Tailwind Submit Button (Form Primary) (opens in a new tab)
hover:, active:, focus-visible: and disabled: read in the order a user meets them.51 Tailwind CSS buttons for form submits and gradient CTAs, add-to-cart and download actions, loading and confirm-delete states, segmented button groups, and icon toolbars — from soft-pill SaaS primaries and neon plasma bursts to skeuomorphic clay, brushed brass, walnut, velvet, boarding-pass, and punch-card surfaces. Scoped under .tb-NN for no-collision pasting, prefers-reduced-motion guarded, framework-agnostic.
Related25 CSS Button Groups20 Tailwind SVG Stroke Buttons34 CSS Floating Buttons20 CSS Loading Buttons28 CSS Close Buttons28 Tailwind CSS Close Buttons
Premium upgradePartner: Untitled UI
Affiliate linkBuilding a whole Tailwind app, not just this component?
You just browsed 51 Tailwind buttons. Untitled UI Pro unlocks 5,000+ components and sections and 250+ page examples for React, styled with Tailwind CSS v4. One licence is a one-time $349 with lifetime updates; teams start at $999.
Untitled UI React. We may earn a commission, at no extra cost to you.
hover:, active:, focus-visible: and disabled: read in the order a user meets them.background-size transition on a fixed gradient — no pseudo-element, no overflow clipping — and the border is currentColor, so one colour utility re-skins the whole control.has-hover: and has-focus-visible: on the wrapper are the port's headline: the relational CSS selector becomes two variants sitting on the element that owns the tooltip.@property to register the angle as an <angle>, which is the clearest case in the collection for keeping machinery in the stylesheet and exposing only the result as animate-tb05-spin.group-data-[state=busy]:: the CSS version needed three descendant selectors keyed off [data-state], and Tailwind states each slot's own visibility condition on the slot itself.disabled, and aria-disabled (focusable, explains itself, refuses activation). The frozen cards use data-force attributes rather than .is-* classes, which is the tidiest thing the port does: demo-only styling is visibly demo-only.has-checked: variant — the state is read where it is rendered, not from four rules at the bottom of a stylesheet.has-checked: turns every downstream reaction into a variant on the element that reacts.0fr → 1fr grid-track animation ports to two utilities, which is the cleanest possible expression of it.clip-path: polygon() with matching point counts, and Tailwind carries them as arbitrary utilities, which puts the play polygon and the pause polygon on the same line of markup for the first time.style attribute — the one place where inline style beats a utility, and the port says so.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 both stacks are single arbitrary utilities, because a glow is one recipe, not six values.shadow-[…] and its active: twin.animationend so the DOM never grows. The shard's look is a class list the JS applies; its trajectory is inline data.backdrop-blur and backdrop-saturate handle the refraction, a bright inner rim catches the light on two edges only, frost collects in the corners, and pressing sends a hairline crack across the face.background-clip:text, and a gloss band that travels across the finish on hover.transform-3d, backface-hidden, perspective-* and rotate-y-* as utilities, so the entire flip construction ports — which it could not have done in v3.aria-live region — so the message is announced, not merely displayed. The animation is garnish on a working disclosure.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, a shortcut chip that dims as the arrow slides, and a focus ring that respects the radius exactly.clip-path, near-black translucency over a blurred backdrop, two facet planes shaded by opposing gradients, and a razor of light that runs along the leading edge on hover.transform-3d construction as the coin.filter transition, exactly how the chemistry 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.role="switch" holds the state.aria-pressed, not just its look.counter-reset and printed through content. Zigzag torn edge, faded thermal ink, monospaced columns.grid-template-areas with every child assigned to the same area. The button's width is therefore fixed by the widest label and never reflows when the state changes. Transitions only touch opacity and visibility, which the compositor handles without a layout pass, so there is no jitter. Drive the state from a single data-state attribute on the button and let attribute selectors reveal the right cell. The common bug is swapping labels with display:none / display:block instead — that collapses the grid cell, so the box snaps narrow on pending and wide again on done. Keep every layer rendered and only fade between them.--angle, --dist and --spin. A single keyframe reads those properties in a translate()/rotate() chain, so one shared animation produces dozens of unique trajectories. The burst is triggered by :active (or :focus for keyboard), and the pieces sit in an absolutely-positioned overlay with inset:0 and pointer-events:none so they never change the button's box or intercept clicks. The catch: a pure :active burst only plays while the mouse is held. If you want a fire-and-settle effect on real clicks you either accept the held-state limitation or add a tiny class-toggle in JS — the CSS still does all the motion.aria-label, so a screen reader announces "Bold", "Italic" and so on even though the face shows only a glyph. The decorative SVG inside each button carries aria-hidden="true" so it is not announced twice. The visible tooltip is purely sighted affordance — it is a separate element revealed on :hover and :focus-visible, never the accessible name, because tooltip text can be clipped or delayed and assistive tech should not depend on it. Each button is a genuine <button type="button"> with a 44px touch target hit area even though the painted size is 40px, satisfying the pointer-target minimum. Don't put the label only in a title attribute: title is inconsistently announced and invisible to touch users.clip-path, and the play triangle and the two pause bars are two states of the same polygon. Toggling an aria-pressed attribute (or a checkbox's :checked) switches to a second clip-path value with the same number of points, and because both paths have matching point counts the browser can tween between them. There is no icon library, no image swap and no layout change — only the clip geometry animates. The subtlety is that clip-path interpolation requires identical vertex counts on both keyframes; if one shape has five points and the other has four, the transition snaps instead of morphing. Author both states as the same polygon topology and move the vertices, and expose the current state with aria-pressed so the control is announced as a toggle, not a plain button.@property and declare syntax: '<angle>' plus an initial value; only then does the engine know how to interpolate it, and the conic gradient sweeps smoothly. Also make sure the animated angle feeds the gradient's from position and that the element actually has a size — a zero-height pseudo-element paints nothing. Because this runs continuously it must be wrapped in a @media (prefers-reduced-motion: reduce) block that sets animation: none, otherwise you ship a perpetually spinning element to users who asked the system to stop motion.<input type="radio"> elements sharing one name, each paired with a <label> that is the visible segment. This buys you three behaviours for free: arrow-key navigation and roving focus within the group, a single serialisable value when the control sits in a <form>, and the selected pill styled purely with the :checked sibling selector. Don't hide the inputs with display:none — that removes them from the tab order and kills keyboard support; use a clip/opacity technique that keeps them focusable, and add a :focus-visible ring on the adjacent label so keyboard users can see where they are. Because the state lives in the radios, the group works with no JavaScript and submits like any other field.disabled button is removed from the tab order, fires no click or focus events, and cannot be the target of a form submit — the browser enforces all of that. An aria-disabled="true" button still receives focus and still fires events; it only announces as unavailable, and you must block the action in your handler yourself. Use real disabled for a control that is genuinely inert. Reach for aria-disabled when you want the button to stay focusable so a keyboard user can land on it and hear why it can't be used — for example a submit button that is greyed until a form validates, where a fully disabled button would be silently skipped and leave the user confused about what is blocking them. Style both with the same dimmed treatment so they read identically.backdrop-blur, the copper coin (27) and punch card (50) with 3D transform, the magnetic mercury (15) tracking the pointer — are showpieces, not defaults. Skip them for high-frequency, high-stakes controls: a checkout's primary submit or an add-to-cart should be instantly legible, and backdrop-blur is one of the more expensive paints on mobile, so a grid of frosted buttons can drop frames. The pointer-following and mercury effects rely on hover and fine pointer movement, so they degrade on touch — always ship a clean fallback state. And every continuously-animated variant (the conic ring, neon idle, spinning reels) must honour prefers-reduced-motion. For dense forms and toolbars prefer the plain primary, ghost or soft-pill buttons (demos 01–03, 32); save the tactile pieces for hero moments where one button carries the page.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.