Button Deluxe v3

A whimsical 3D pure CSS button




Button Deluxe is a further development/refinement/SASS-integration of Josh Comeau's whimsical 3D Button. It differs in quite a few places, but the main differences are:

  • Addition of a switchable button type, supporting an .active class/state
  • Extensive use of CSS variables, calculation and HSL colors to make buttons more easily customizable
  • Button 'masks' itself, to facilitate the switchable version (which enables 'sinking' into the page when active)
  • Use of regular box-shadow instead of a separate shadow span (unavoidable because of the previous point)

SASS source/compiled CSS files available at the bottom of this page.

Bootstrap replacements/integration

Below are Bootstrap's native btn buttons, and their 'deluxed' counterparts. Integration is based on bootstrap 5.1.3, streamlined using its native SASS files as much as possible.


Deluxe variant

<button type="button" role="button" class="btn btn-primary">
<button type="button" role="button" class="btn btn-deluxe btn-primary">
    <span class="btn-deluxe-front">Primary</span>
    <span class="btn-deluxe-edge"></span>
Primary (as <a role="button">)
Primary (as <a role="button">)

Outline variants

Note: using webfonts with outlined btn-deluxe buttons can cause miscalculated widths in Chrome (due to resulting fractional pixel values). This will cause the button's front to miss a right border in some situations. The only workaround is to parse each button with an outline style, and specifically set a rounded-off (ceil()'d) pixel width, like so:

document.querySelectorAll('.btn.btn-deluxe[class*="btn-outline-"]').forEach(function (el) {
    el.style.width = `${Math.ceil(parseFloat(window.getComputedStyle(el).width))}px`;


Deluxe variant

<button type="button" role="button" class="btn btn-outline-primary">
<button type="button" role="button" class="btn btn-deluxe btn-outline-primary">
    <span class="btn-deluxe-front">Primary</span>
    <span class="btn-deluxe-edge"></span>

SASS/CSS files:

  • btn-deluxe-v3.scss (SASS) - btn-deluxe-v3.css (compiled CSS)

    Foundation for all btn-deluxe elements, using the default color scheme (see first two buttons)

  • btn-deluxe-bs-v3.scss (SASS) - btn-deluxe-bs-v3.css (compiled CSS)

    Same as above, but with additional CSS for full integration in Bootstrap (5). Make sure you add the two required inner span elements: btn-deluxe-edge and btn-deluxe-front (the latter containing the button's label/text) to each button, and add the btn-deluxe class to all btn elements.

    The SASS source requires:

    • btn-deluxe-v3.scss (see above)
    • _functions.scss (native Bootstrap 5 SASS)
    • _variables.scss (native Bootstrap 5 SASS)
  • btn-deluxe-milspec.scss (No link yet; still a work in progress)

    Provides military-style buttons, like those seen in cold war-era Titan missile silo launch control centers.


Use the (commented) variables in btn-deluxe-v3.scss to change styling and behaviour, and/or:

Each button has its basic styling set using CSS variables. These are slightly limited compared to the variables in the SASS source, but still allow a fair amount of customization. You can write your own CSS class to override/customize these parameters:

  • --but-deluxe-txt (hex) Text color
  • --but-deluxe-hue (deg) Hue for button base color
  • --but-deluxe-sat (%) Saturation for button base color
  • --but-deluxe-bri (%) Lightness/brightness for button base color
  • --but-deluxe-alt-hue (deg) Hue for color used in gradient
  • --but-deluxe-alt-sat (%) Saturation for color used in gradient
  • --but-deluxe-alt-bri (%) Lightness/brightness for color used in gradient
  • --but-deluxe-shade-angle (deg) Angle for button gradient
  • --btn-deluxe-trans-duration (ms/s) Basic animation speed
  • --btn-deluxe-round (px/em/rem) Roundness of button's corners
  • --btn-deluxe-up (px/em/rem) 'Thickness' of button in the 'up' (non-depressed) state
  • --btn-deluxe-shadowsize (px/em/rem) Size of the button's shadow


  • When zoomed in on iOS safari, Bootstrap's active class toggling (via data-bs-toggle="button") seems to be delayed somewhat, causing the button to 'pop up' again first before entering active state.
    Should this be a problem, you can try overriding the toggling of active classes, for example by binding them to the 'pointerdown' event, which fires earlier than a regular 'click' handler. You can see the difference it makes on this page (zoom in and try the 'native' switchable Button Deluxe (top of page) which activates on pointerdown, and then try the bootstrap variants).