CSS & HTML

CSS Gradient Buttons with color-mix() and OKLCH

By Aigars Silkalns Updated 11 min read

CSS Gradient Buttons with color-mix() and OKLCH

A gradient button in modern CSS is one linear-gradient() whose two colors are computed from a single base color with color-mix(). You change one custom property, --base, and the highlight, shadow, border, hover and pressed states all follow. No images, no JavaScript, no vendor prefixes, and it works on a, button and input alike.

This is the current version of a technique that once needed three background declarations per color (one each for WebKit, Firefox and an Internet Explorer filter). Today every browser reads the same standard syntax, so the work shifts from “does it render” to “is it readable, consistent and easy to retheme”. The code below is checked for that: every text and gradient combination meets WCAG contrast.

Gradient buttons in orange, blue, green, dark and purple, three sizes, ghost variants, the normal, hover, active, focus and disabled states of a green button, and icon and loading buttons
The finished set: five colors, three sizes, a ghost variant, five states, an icon button and a loading button, all from one stylesheet.

What you get

  • Pure CSS, scalable with font-size because padding, gap and radius use em.
  • One base color per variant. Gradient stops, border and focus ring are derived from it.
  • Normal, hover, active, :focus-visible, disabled and loading states.
  • A solid-fill ghost (outline) variant, and icon plus label buttons.
  • A smooth hover transition on the gradient itself, which plain CSS could not animate for years.
  • Respects prefers-reduced-motion and Windows high-contrast (forced colors) mode.

The complete stylesheet

Here is everything. The sections after it explain each decision, so you can change the look with confidence.

@property --lift {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}

.button {
  --base: oklch(0.72 0.17 55);          /* orange; change this one value */
  --ink: oklch(0.2 0.05 55);
  --top: color-mix(in oklch, var(--base), white calc(18% + var(--lift)));
  --bottom: color-mix(in oklch, var(--base), black 14%);

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  padding: 0.6em 1.6em;
  border: 1px solid color-mix(in oklch, var(--base), black 35%);
  border-radius: 0.5em;
  background: var(--base);              /* fallback if the gradient below is invalid */
  background: linear-gradient(to bottom, var(--top), var(--bottom));
  box-shadow:
    inset 0 1px 0 color-mix(in oklch, white 45%, transparent),
    0 1px 2px rgb(0 0 0 / 0.25);
  color: var(--ink);
  font: 600 1rem/1.2 system-ui, sans-serif;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: --lift 150ms ease-out, translate 80ms;
}

.button:hover {
  --lift: 12%;
}

.button:active {
  --top: color-mix(in oklch, var(--base), black 14%);
  --bottom: color-mix(in oklch, var(--base), white 14%);
  translate: 0 1px;
  box-shadow: inset 0 1px 3px rgb(0 0 0 / 0.3);
}

.button:focus-visible {
  outline: 3px solid color-mix(in oklch, var(--base), black 40%);
  outline-offset: 3px;
}

.button:disabled,
.button[aria-disabled="true"] {
  opacity: 0.55;
  cursor: not-allowed;
  pointer-events: none;
}

/* Colors: each --base/--ink pair is contrast-checked at every gradient stop */
.button.blue   { --base: oklch(0.38 0.17 255); --ink: white; }
.button.green  { --base: oklch(0.68 0.17 145); --ink: oklch(0.18 0.05 145); }
.button.dark   { --base: oklch(0.34 0.02 260); --ink: white; }
.button.purple { --base: oklch(0.38 0.18 300); --ink: white; }

/* Sizes */
.button.small { font-size: 0.8rem; }
.button.large { font-size: 1.3rem; }

/* Ghost: transparent fill, border and text derived from the same base */
.button.ghost {
  --ghost: color-mix(in oklch, var(--base), black 50%);
  border-color: var(--ghost);
  background: transparent;
  box-shadow: none;
  color: var(--ghost);
}
.button.ghost:hover {
  background: color-mix(in oklch, var(--base) 12%, transparent);
}
.button.ghost:active {
  background: color-mix(in oklch, var(--base) 22%, transparent);
  box-shadow: none;
}

/* Icon + label: the SVG inherits the text color */
.button svg {
  width: 1.1em;
  height: 1.1em;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Loading: a spinner built from the border, no extra markup */
.button[aria-busy="true"] {
  cursor: progress;
  pointer-events: none;
}
.button[aria-busy="true"]::before {
  content: "";
  width: 1em;
  height: 1em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: spin 700ms linear infinite;
}
@keyframes spin { to { rotate: 1turn; } }

/* No color-mix(): flat colors instead of a gradient */
@supports not (color: color-mix(in oklch, red, blue)) {
  .button { background: var(--base); border-color: currentColor; }
}

@media (prefers-reduced-motion: reduce) {
  .button { transition: none; }
  .button[aria-busy="true"]::before { animation-duration: 3s; }
}

@media (forced-colors: active) {
  .button { border-color: ButtonText; }
}

Use it like this. Links and buttons share the same class:

<a href="/pricing" class="button">See pricing</a>
<button type="submit" class="button blue large">Subscribe</button>
<a href="/docs" class="button ghost green">Read the docs</a>

<!-- Icon + label: hide the decorative icon from screen readers -->
<button type="button" class="button blue">
  <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v12m0 0-4-4m4 4 4-4M5 21h14"/></svg>
  Download
</button>

<!-- Loading -->
<button type="submit" class="button blue" aria-busy="true">Saving</button>

<!-- Disabled -->
<button type="submit" class="button green" disabled>Disabled</button>

To try it, paste the stylesheet into a <style> tag in any HTML file, add the markup above, and open the file in a browser. Nothing needs a build step.

Build the gradient from one color with color-mix()

The old approach hard-coded two hex values per button, and every new color meant picking them by eye. Here, --top is the base mixed with white and --bottom is the base mixed with black:

--top: color-mix(in oklch, var(--base), white 18%);
--bottom: color-mix(in oklch, var(--base), black 14%);

color-mix() is Baseline widely available, in all major browsers since May 2023. The in oklch part picks the color space used for mixing. OKLCH describes color as lightness, chroma (how colorful) and hue, and lightness follows what the eye actually sees. Mixing a color with white in OKLCH gives an even, natural highlight. The same mix in plain sRGB tends to go chalky or shift hue, especially for blues.

The base itself is written as oklch(0.72 0.17 55): 72% lightness, 0.17 chroma, hue angle 55 (orange). To make a new variant, change the three numbers. Keep chroma similar and rotate the hue, and your buttons will feel like one family. Lightness is the number to choose carefully, because it decides which ink color can sit on top (see the contrast section):

.button.purple { --base: oklch(0.38 0.18 300); --ink: white; }

The states

State What changes Why
Normal Top-to-bottom gradient, 1px darker border, soft shadow, inset highlight Reads as a raised, pressable surface
Hover Gradient gets lighter Confirms the pointer is over a target
Active Gradient reverses, button moves down 1px, shadow goes inset Feels physically pressed
Focus (keyboard) 3px outline with an offset, via :focus-visible Keyboard users must always see where they are
Disabled Faded, no pointer events Signals the action is unavailable
Loading Spinner before the label, clicks ignored Prevents double submits and shows progress

Notice that :active only redefines --top and --bottom. Because the gradient is built from variables, you never repeat the background declaration per state or per color. That is the main maintainability gain over the old stylesheet.

Use translate: 0 1px rather than the old position: relative; top: 1px. The individual translate property is supported in all major browsers since 2022, does not affect layout and does not clash with other transforms you might add later.

How the screenshot forced the states

You cannot hold a real :hover or :active still for a screenshot, so the demo page copies the same declarations onto helper classes (.hov sets --lift: 12%, .act repeats the :active rules, .foc repeats the outline). Those helpers exist only in the demo page. In your project, use the real pseudo-classes exactly as in the stylesheet above, and never ship .hover classes. To inspect a real state, open DevTools, select the button and tick :hover, :active or :focus-visible in the Styles panel.

Animate the gradient on hover with @property

Browsers cannot interpolate between two gradients: background switches instantly even with a transition. The fix is to animate a number instead. Registering --lift with @property tells the browser it is a percentage, so it can be transitioned:

@property --lift {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}

.button { transition: --lift 150ms ease-out; }
.button:hover { --lift: 12%; }

The gradient reads --lift in its top color, so the highlight brightens smoothly instead of snapping. @property has been supported in all major browsers since July 2024 (check the MDN compatibility table for the current Baseline label). In a browser without it, the hover still works, it just changes instantly. That is a graceful fallback, not a breakage, and it needs no extra code.

Contrast: check every point of the gradient

Text must reach 4.5:1 against the background it sits on (WCAG 1.4.3). On a gradient that means the worst stop counts, and the worst stop is not the one you see at rest. Hover lightens the top stop (18% white plus 12% lift is 30% white), and the pressed state swaps the stops. A button that passes at rest can fail the moment the pointer arrives.

I measured every variant at the normal top and bottom stops, the hover top stop, and both pressed stops. Light inks need dark enough bases, because hover only makes the top lighter:

Variant Ink on base Lowest ratio at any stop Where it occurs
Orange oklch(0.72 0.17 55) Dark brown oklch(0.2 0.05 55) 4.76:1 Bottom stop
Green oklch(0.68 0.17 145) Dark green oklch(0.18 0.05 145) 4.69:1 Bottom stop
Blue oklch(0.38 0.17 255) White 4.53:1 Top stop on hover
Purple oklch(0.38 0.18 300) White 4.77:1 Top stop on hover
Dark oklch(0.34 0.02 260) White 5.13:1 Top stop on hover

An earlier draft of this stylesheet used a mid blue, oklch(0.62 0.16 250), with white text. It looks pleasant and fails: about 2.8:1 at the normal top stop and about 2.3:1 at the hover top stop. The fix is not a darker ink (white is already the maximum) but a darker base. As a rule of thumb, white ink needs a base lightness of about 0.38 or less in OKLCH with this lift, and dark ink needs a base of about 0.72 or more.

Two more contrast rules apply. The focus outline and the ghost button’s border are UI components and need 3:1 against the page (WCAG 1.4.11); both are mixed with 35 to 50% black, which clears that on a white page. If your buttons sit on a dark or colored page, recheck against that background, not white.

Ghost buttons

A ghost (outline) button drops the fill so it works as a secondary action next to a solid one. It reuses the same --base, so recoloring still takes one line:

.button.ghost {
  --ghost: color-mix(in oklch, var(--base), black 50%);
  border-color: var(--ghost);
  background: transparent;
  box-shadow: none;
  color: var(--ghost);
}
.button.ghost:hover {
  background: color-mix(in oklch, var(--base) 12%, transparent);
}

The text color is the base darkened by half, which gives 11:1 or more on white for the orange, blue, green and dark variants (the tinted hover background keeps it above 10:1). Using the raw base as text would fail for the lighter orange and green.

Icon and loading states

Because .button is inline-flex with a gap, an inline SVG icon sits beside the label with no extra margin rules. Give the SVG stroke: currentColor so it inherits the button’s ink and passes the same contrast check, and mark it aria-hidden="true" when the label already says what the button does. An icon-only button needs an aria-label instead.

The loading state is a CSS spinner drawn on ::before, so the markup does not change. Set aria-busy="true" from your script when the request starts and remove it when it ends, and keep the label text meaningful (“Saving”). Under prefers-reduced-motion the spinner slows to a gentle three-second turn rather than disappearing, so there is still a visible sign that work is happening. Note that inline-flex does not apply to <input type="submit">, which cannot hold child content: use <button> for icons and spinners.

Accessibility checklist

  1. Contrast. Verify the worst gradient stop, including hover and pressed, as shown above. Check both ends, not just the middle.
  2. Focus. Never write outline: none without a replacement. :focus-visible shows the ring for keyboard focus and hides it for most mouse clicks.
  3. Reduced motion. The prefers-reduced-motion block removes the hover transition for people who asked their system for less movement.
  4. Forced colors. In Windows high-contrast mode the browser replaces your colors. Keeping a real border (set to ButtonText) means the button still has an edge.
  5. The right element. Use a for navigation and button for actions. A plain disabled attribute only works on real buttons. If you must style a link as disabled, add aria-disabled="true" and stop the click yourself:
document.addEventListener('click', (e) => {
  if (e.target.closest('a[aria-disabled="true"]')) e.preventDefault();
});

The stylesheet also sets pointer-events: none on disabled controls, which blocks mouse clicks, but keyboard activation of a link still needs the script. A link that does not go anywhere yet is often better left out of the page than shown disabled.

About the disabled look

The disabled style fades the button to 55% opacity, which drops its text below 4.5:1. That is allowed: WCAG 1.4.3 exempts text in inactive user interface components. The trade-off is real, though, because people with low vision may not read a faded label at all. If the reason a control is disabled matters (“Add a payment method first”), put that reason in visible text next to the button instead of relying on the faded look.

Use it with design tokens or Tailwind

The pattern needs only three values per variant, so it maps cleanly onto a token system. Keep the color tokens in your own variables and let --base point at them:

:root {
  --color-brand: oklch(0.38 0.17 255);
  --color-brand-ink: white;
}
.button.brand { --base: var(--color-brand); --ink: var(--color-brand-ink); }

In Tailwind CSS 4, theme colors are CSS variables, so the same idea works with arbitrary properties. Define the shared .button rules in a @layer components block of your CSS, then set the base per use:

<button class="button [--base:var(--color-blue-900)] [--ink:white]">Subscribe</button>

Any color token works as long as you re-run the contrast check for its lightness. Tailwind’s default palette is also defined in OKLCH, so color-mix(in oklch, …) blends it predictably.

Fallback for older browsers

The first background declaration, background: var(--base), is deliberate. If the browser cannot compute the color-mix() values, the gradient declaration is invalid at computed-value time and the button would otherwise render with no background at all. The flat declaration is not enough on its own, though, because the border and focus ring also use color-mix(). This block gives them solid values when mixing is unsupported:

@supports not (color: color-mix(in oklch, red, blue)) {
  .button { background: var(--base); border-color: currentColor; }
}

Both oklch() and color-mix() reached all major browsers in 2023, so this is a safety net for stale embedded browsers rather than a common case.

Resize and recolor

Because the padding, radius and the inset highlight all scale with em (and the border is a fixed 1px), a size class only needs a font size:

.button.small { font-size: 0.8rem; }
.button.large { font-size: 1.3rem; }

Prefer rem over a fixed pixel size so buttons follow the user’s browser font setting. The same custom-property approach works for any variant: define --base, pick the ink that clears 4.5:1 at every stop, and you are done.

Should you still use gradients?

Flat buttons dominate current interfaces, but a subtle top-to-bottom gradient is far from dated. It adds depth and a clearer affordance on dense, tool-like screens, marketing pages and anywhere buttons compete with a busy background. Keep the contrast between the two stops small and let the border and shadow do part of the work. Heavy glossy gradients are what made 2010 buttons look old.

Where to go next

198 comments from the archive
  1. Very nice tutorial! Great job!

    Martijn: With all do respect, your solution isn’t even comparable with this.
    In fact, is’t been commonly used for over 3-4 years and is found all over the web, (i.e use of .png images and background-position). This solutions uses ONLY css and no images which decreases number of requests to the server and the download-time in the long run :-)

  2. What is the name of this handwriten typeface you use in your articles? I’d appreciate if you could tell me! Thx! ^^

  3. Quite simple, nothing new.

    But it’s good to see another example of CSS3 usage !

  4. My only qualm with using gradients on buttons at the moment is that you currently lose the -webkit-transition on hover. Unless I’m missing something. It certainly doesn’t work for Safari and Chrome Windows.

    Anyone know when transitions between gradient backgrounds is likely to be in place?

  5. I guess you should add -ms-filter for IE8+

    Beside that, this technique is perfect :)

  6. I’m new to your site and love the content so far! These buttons are great, love the use of all CSS and HTML. Thanks for sharing!

  7. Great tut Nick. I’ve seen a few like this over the last few days, but yours is the neatest by far. Could the same code work to create a circular button I wonder?

  8. Great piece Nic and the #smashingmag tweet could not have come at a better time! I’m revamping a client’s old site – and working on CSS3 buttons this am. Now I have a template to start from! Saved me at least an hour. I’ll credit you on the site.

    Muchas gracias!

  9. Thanks so much for that clear explanation. I’ve used a lot of those techniques before, but I love the idea of making the text darker on the :active state. You learn something new every day!

  10. I’ve been using box-shadow to make buttons, I’ll need to try adding in the gradient effect as well! I’m really loving all the ways CSS3 can replace images. Here’s some social networking icons/buttons I made without any images at all, using CSS3.

  11. This is actually really well done. Thanks for taking the time of clearly outlining the process here. You’ve done well to answer questions before they inevitably arise (cross-browser issues). I’ve got a project these babies can go into today.

  12. You can add an inset box-shadow to the active state to really make it feel pressed.

  13. This is an excellent article!! One thing you haven’t covered which readers may find useful are “color stops” which give you more control over the gradient in a very easy way. Both the Webkit and Mozilla engines support color stops.

    Webkit’s implementation is better in my opinion as you can specify where the stop point is e.g. for a 3 step gradient you may do something like this:
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0.0, yellow), color-stop(0.5, orange), color-stop(1.0, red));
    The first parameter in the color-stop attribute is the percentage of distance from start and end points.

    Mozilla’s implementation still provides color stops but you have no control over their placement – e.g.
    background: -moz-linear-gradient(left, yellow, orange, red);

  14. Thanks for the great css tips, & the amazingly clear explanations & diagrams. And especially for helping create more beauty on the web – functional & aesthetic!

  15. @Shane: you can specify where color-stops are in Mozilla, too:
    background-image: -moz-linear-gradient(top,
    rgba(255, 255, 255, 0.6) 0%,
    rgba(255, 255, 255, 0.3) 40%,
    rgba(255, 255, 255, 0.2) 40%,
    rgba(255, 255, 255, 0.0) 100%);

  16. anyone has idea why:
    input element in Opera 10.51
    dosent work?
    it hasent got text drop-shadow
    its strange since other button work quite well…

  17. Nice tutorial, the code is clear and easy to understand. Thank you very much for sharing, I’ll use it in my website right now.

  18. While I don’t agree with using Microsoft filters to ape CSS3, you have omitted the declarations for IE8, e.g.:

    -ms-filter: “progid:DXImageTransform.Microsoft.gradient(startColorstr=’#faa51a’, endColorstr=’#f47a20′)”;

  19. The technique is great, and you made it really easy to understand and use,
    but what really amazed me was your wonderful choice of colors.

    I don’t think a tutorial can be made any better than what you’ve done here!

    Keep up the good work.

    Mamdouh

  20. This is very cool indeed, nice way to have more styled up basic buttons on a site without using images — but it’s still very annoying you have to use all the browser specific code. :(

  21. This + ie-css3.htc and you’ve got complete coverage!
    This is great!
    Thanks!

  22. I also agree that this technique is great, and you made it really easy to understand. This is a brilliant tutorial, keeping it simple by using no image or Javascript, and simply using CSS. The gradient adds a nice pop to a simple button, to add a different look to spice up your web design. The button states make it completely user friendly with the hover and active shades. The way you broke it down is brilliant for those just starting out

  23. Another brilliant, well written, well explained, and actually useful tutorial. Love that you don’t ignore cross browser compatibility / degradation as well. Thanks Nick!

  24. This article is one of the best CSS tutorials I´ve ever read! THANKS FOR SHARING THIS!
    (I´m really glad I´ve joined your subscriber´s feed!)
    Rosamunda

  25. Once again, lame IE ruins innovation by not displaying gradients. Man, this could really save a lot of page load time throughout the web if everyone could use css buttons instead of PNGs.

  26. Nice looking buttons… they’re scalable too which is a must for easy updating. Shame about IE but anybody who uses IE doesn’t deserve to see pretty buttons anyway ;)

    Justin – Big Click
    @bigclickstudios

  27. Nice buttons. And thanks for the code. This is awesome!
    I will make smth similar for the website I’m working on. Thanks.

  28. Nice bit of CSS here, have just tried it and it works a treat in my decent NON IE browsers.

    Even In IE however it still looks ok and has a nice gradient, just no shadow or rounded corners.

    Cheers for sharing this with us… most appreciated.

  29. Brilliant and wonderful.

    One problem, I am still seeing a ‘double padding’ making the button twice as wide in IE 7. Any thoughts on a fix?

  30. For more compatibility to others browsers. For every property with prefix -moz or -webkit, you may also incluse the prefix “-khtml”.
    Like:
    -moz-border-radius: 10px;
    -khtml-border-radius: 10px;
    -webkit-border-radius: 10px;

  31. Neat professional buttons! I’ll use them!

    Approx. 20 line of CSS and it looks beautiful.

    Thx for sharing.

    ubuntu admin

  32. Is there a way to get rid of the dotted line around the text when clicking on the button?
    I figured I had to add outline:none to the .button:active, but I see it’s already applied to the .button?

  33. First of all, buttons look great.

    Second, is there a way to get the form elements (input/button) to be the same height as the rest in Firefox (I’m using 3.6.3)? Open the demo in ff to see what I mean.

  34. I like the techniques you’ve outlined here. I’ve used some css3 features in recent projects and like the way rounded corners and such, nicely degrade into simple square boxes in cruddy old IE.

    Thanks for the post.

  35. I know you heard it often… but great work..
    i’m using them now….

    Thanks
    Kees.

  36. Hey, thanks for such a great feature. I’m having trouble implementing it on chrome though. When I type in my button text (ie the text that shows on the button) in upper case, either just the first letter shows up or nothing at all. It works fine in firefox and IE though. I had to revert to lowercase but will really like to use all caps. Help please?

  37. Never mind. Just had to take out the text shadow code. Works in uppercase on chrome now.

  38. Hey, did you try a Cool Button Designer? I think it is a best web button creator – if you dont have a photoshop or dont know how to use it. You can also apply the CSS to the graphics created in Cool Button Designer. I suggest you to try it, it has bunch of features, and creates a cool looking buttons. Just google for Cool Button Designer.

    Chao.

  39. great tutorial, i never knew you could do that in pure css. It woudl be good to see how it degrades in each browser, or a browser compatability chart?

  40. What if you want an icon-image in a input-button ánd a gradient?
    Impossible right?

  41. thank you for this lovely post is there any way we can hack IE for rounded corners

  42. Very good and interesting Article, but that sucks that Internet Explorer has problems with this button…

  43. Love it, works great… Mozilla and Chrome it is perfectly fine just IE it is not perfect… but enough for use :)

  44. Nice, but what is the proper CSS3 gradient property once it is all finally supported?

  45. Also, for Opera you can use a box-shadow inset to simulate a gradient on the button. Just set the bg color, set the inset shadow, then declare your gradients. It’s not perfect, but looks better than plain color…

  46. Is the only way to actually submit a form with this technique to use javascript?

  47. Nice, but what is the proper CSS3 gradient property once it is all finally supported?

  48. @ Randall – I just came across this post and I’m seeing the same thing with “double padding” in IE. Did you find a fix?

    Great buttons otherwise!

  49. Oh, wow! Thank you so much for this! I’m still learning what you can do with CSS3. I am really happy to find this. It will help save time when designing websites and no longer having to design buttons. Thanks again for this and the other great articles on this site.

  50. Very handy. Thanks for sharing the code and how to please all browsers – even IE.

  51. All of these rounded corner tuts are great and fun to experiment with, but since IE doesn’t support border-radius, they have no real value…

  52. These are really nice looking. Great work! An improvement would be to make all the gradients semitransparent black instead of whatever color the background is. That way, the only thing you have to change to get a different colored button is a single simple hex background color. The black gradient would control the slight darkening on every single one. More flexible, less code.

  53. Really nice. It’s great what you can do with bare CSS – isn’t it? Actually it would have been a bit better with the use of CSS transitions. Could be like this:

    .blue {
    color: #d9eef7;
    border: solid 1px #0076a3;
    background: #0095cd;
    background: -webkit-gradient(linear, left top, left bottom, from(#00adee), to(#0078a5));
    background: -moz-linear-gradient(top, #00adee, #0078a5);
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00adee', endColorstr='#0078a5');

    -webkit-transition: background 1s linear;
    -moz-transition: background 1s linear;
    transition: background 1s linear;
    }

    That gives the background a nice animation effect when its hovered. I think that should be part of a css3 tutorial.

  54. Dreiden, you clearly have no idea. Have you ever heard the term “progressive enhancement”? Many very prominent sites are using border-radius already. It’s idiots like you who are holding the progress of the web back.

  55. Great article! I’m from Brazil, I’ve been following your works for a while and I’m always learning new things from you, congratulations.

  56. thanks for sharing this code
    but is there a fix to the height in firefox?
    Thanks you

  57. Wow! Those buttons looks cool! Thanks for sharing that!

    But… is that WC3 compliant? Is that a ‘valid’ code?

  58. It seems the filter syntax breaks things in the new Explorer 9 beta. It gives the button/form button a solid background color + the rounded borders on top of it. The end result is as you can imagine not very nice. Any ideas on how to fix this? Thanks for a great site!

  59. I guess the alternative is to move the filter property to a IE8 only css with a conditional comment.

  60. Hey guys, this script is really amazing as you say. but didn’t you experience any height problem with firefox???
    i already asked that a month ago but had no answer. any tips???

  61. First, thank you for sharing your work, it’s very useful !

    Just a little detail I have added to your buttons for firefox :

    /* delete useless outline in firefox elements */
    button::-moz-focus-inner,
    input[type=”reset”]::-moz-focus-inner,
    input[type=”button”]::-moz-focus-inner,
    input[type=”submit”]::-moz-focus-inner,
    input[type=”file”] > input[type=”button”]::-moz-focus-inner {
    border: none;
    }

    With that, it will delete the dotted outline in firefox for the buttons/inputs elements.

    An other detail, I put a font-weight: 600; to be more readable in chrome because the text seems to be more aliased in chrome.

    Thank you !

  62. hey Thomas, thanks for the tip to remove the useless outline in FF.
    don’t you have a height issue with FF???

  63. Great article! I’m from Brazil, I’ve been following your works for a while and I’m always learning new things from you, congratulations.

  64. Great article! I’m from Brazil, I’ve been following your works for a while and I’m always learning new things from you

  65. @Reda : No, I do not have any issue with FF. Which version do you use ? I’m currently with 3.6.10.

    Do you experience an issue on each html elements with a button css class or specific elements ?

  66. wow, my text has been cut because i put sime html tags:
    “Hi Thomas, and thanks a lot for your answer.
    I’m using FF 4 on a mac and all <button> elements have a much bigger height, i have no problem with <a> buttons.
    if you don’t see what i mean, i can send you a screen…
    Thanks once again”

  67. @Reda :

    Try this :

    button, .button,
    button[type=”button”], button[type=”reset”], button[type=”submit”],
    input[type=”reset”], input[type=”submit”], input[type=”button”] {
    display: inline-block;
    /* zoom and *display = ie7 hack for display:inline-block */
    zoom: 1;
    *display: inline;
    vertical-align: baseline;
    margin: 0px 2px;
    outline: none;
    cursor: pointer;
    text-align: center;
    text-decoration: none;
    padding: 5px 13px 5px 13px;

    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
    border-radius: 5px;

    -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.2);
    -moz-box-shadow: 0 1px 2px rgba(0,0,0,.2);
    box-shadow: 0 1px 2px rgba(0,0,0,.2);

    border-width: 1px;
    border-style: solid;

    font-weight: 800;
    text-shadow: 0 1px 1px rgba(0,0,0,.2);
    }

  68. Hi Thomas, and thank you for your help.
    i did what you recommended but didn’t work out. you said /* zoom and *display = ie7 hack for display:inline-block */ actually the problem is Firefox.
    here is a screen of the difference between Safari and FF:
    and here is the page
    Thanks once again

  69. hello everybody, it’s almost 2am in here and thought it would be better if i share with you what i have just discovered before i go to bed!
    to solve the FF height problem all you need to do is to add the following to .button class:
    font-family: your favorite font;
    font-size: Xpx;
    padding: 5px 10px;
    font-weight: bold;

    and then add this class:
    button::-moz-focus-inner {
    border: 0;
    }
    it’ll also solve many other problems caused by ie6&7 and also with FF such as a black border in ie7…
    hope this will help some of you and also spare you some time… ;)
    chears and thanks again for this nice script and also to Thomas who tried to help ;)

  70. These examples are definitely better then what I was going for and I will be utilizing them instead. These examples display the ‘box-shadow’ throughout. I am still trying to figure out if/how ‘box-shadow’ can be ‘activated’ when hovering over an element. Here is what I *was* going for:

    http://www.lilinks.com/gp/boxshadow/box.html

    Thanks in advance.

  71. Has someone tried to get border radius in Internet Explorer? I’ve tried it with Curvycorners, but it doesn’t work… :-(

  72. Has anyone actually, been able to get a different colour for the “clicked” button, ie visited ?

  73. Excellent stuff guys. I’ve been looking for info on how to do this for a few days. You’ve explained it far better than anyone else I’ve found. Bookmarking the site.

  74. Thanks so much, Nick, for this tutorial and resource! The code works great and the buttons are beautiful. My clients are tickled, too! Much appreciated!!!

  75. I am currently using this technique on my website(in the nav) with some other effects.

  76. Apple’s launch of the iphone 4 white has seen the greatest excitement for a new phone ever, with HD video recording, a super high-res screen and ridiculously slim dimensions, it’s not hard to see why its so popular in the world.

  77. I just want to say that I refer back to this all the time. Not only is it great for making buttons, but you have all the border, gradient, and drop-shadows laid out real nice. Its so convenient to come here, copy the text, paste, and then tweak. Thanks for putting all these rules in one spot like this!

  78. This works great in FF on my PC, but on my MAC, the Gradient does no apply? Am I missing something?

  79. Thank you soo much for this, I was pulling my hair out trying to this.
    Easy explanation and thanks for the code.
    Any idea how to add the when active state? if not many thanks anyway!
    :o)

  80. Brilliant tutorial and Ive now created as many buttons as there are on the demo page! Brilliant work around for the cross browser compatibility too! Thanks a bunch and now I shall do another tutorial!

  81. I’ve been around for quite a lot of time, but finally decided to show my appreciation of your work!

  82. I think it also helps to use border-style: outset; the button and border-style: inset; for an active button. Makes it look more 3d.

  83. Preview

    The image below shows how the button will display in different browsers. In this image it would seem that the CSS3 does stand out a lot better then using the other browsers. It’s clearer to see and the finish is a lot better specially with the rounded corners it also stands out more then the rest.

  84. Its really nice to get buttons like this just by css. But its looks really bad in IE…
    is there any condition on doctype as well for it to work in internet explorer?

  85. For anyone looking to include an icon with the image, simply add a small bit of CSS for your images. Here’s what I used:

    .button img {vertical-align: bottom;}

    You can see a sample at this link. Hope that helps!

  86. This is great work, but i wish there was disable style on it too, lots of people miss out the disable version of the button, which is important too in dynamic sites, specially with ajax involvement. if you guys can add disable version, i would really appreciate it and other people with me.

  87. css3 like WordPress3.0 hit the unknown possibilities of their new. Thanx so much!!!

  88. Hello :) I love this technique and have been using it a lot, so thanks. I was just wondering if you’d looked at it in IE9. It seems to display the background gradient outside the rounded corners, so you have a rounded button within a rectangular button, if that makes sense. It’s hardly a huge deal, and it might be fixed pretty soon (hopefully!) but I was just wondering if you knew any ways around this? Cheers!

  89. IE9 release coming soon and the gradient peek a boo bug is still not fixed. You will need to use a png for IE9 and disable the filter using a conditional html comment. IE9’s filters have not been updated and probably never will, as more css3 features will hopefully be added in future versions. So much for being compliant since IE said that the radius css thye support is complete???

  90. Amazing!great examples. I am so glad that we can do nearly everything with css and reduce the amount of imgs. Thank you

  91. I like it, but I don’t think our designers will like having our styles down-grade. Good solution, but now now I must live in png land.

    1. There can still be a significant load time improvement by using CSS3 instead of images. Big time if you are not spriting(spelling is for people smarter than me) by avoiding another request, but even if you’ve got it in a sprite we’re still talking the difference between bytes and kilobytes. I still think there is a hefty benefit to using css3 where you can and falling back to images where necessary.

      p.s. You in S’toon?

  92. Quite disappointed again with Microsoft. The new IE9 doesn’t support css3 gradients and text-shadows, which are my favourite codes :(

  93. Quite disappointed again with Microsoft. The new IE9 doesn’t support css3 gradients and text-shadows, which are my favourite codes :(

    1. I’ve added support for IE9 with these buttons via SVG + IE9 conditional style block.

    2. My sentiments exactly. All the hullabaloo about “the same mark-up” and we’re still needing separate IE styles, separate IE conditionals and still wasting time trying to support IE.

      The bane of web development.

  94. Indeed a great way to learn how to put the CSS gradient feature in a good practical use.

  95. “The best part about this method is it can be applied to any HTML element such as div, span, p, a, button, input, etc.” Wow!

  96. This is super cool, but to create additional colors – for example brown or yellow ones – it would be interesting to know how you came up with the the color palette.

  97. Great tut, im wondering if the hover state is too heavy using the gradients, specially on IE ?

  98. Very nice! Where did you come up with the gradients? I need one with a different shade of blue. Thanks for the awesome work!

  99. Really nice work!

    Was excited until I previewed it in IE9 on Win7 :(
    Look flawless in Firefox 4 and Chrome 10 though :)

  100. Awesome! Thank you for making this tutorial available. These css buttons are exactly what I am looking for. Cheers, Chris.

  101. @Jacob V

    Hi all, what about the problem in IE9 (all button corners seems to be not transparent) ???

  102. Hi,
    Thank you for the buttons, they are really cool. I have just one problem in that when I use the W3C CSS Level 3 Validator it brings up 56 errors and 1 warning, so can these be properly validated ?
    Thanks

  103. Thanks – these are great. Flipping IE9 gives a rounded border but doesn’t round the background colour to match. I’ve tried clipping the background but couldn’t get it to work. Anyone got a handy solution?

  104. Love all the CSS3 stuff I learn on here.
    I like the fact you show what they look like on non CSS3 browsers.
    Then you can decide whether to go ahead or not…

  105. Is there will be any delay if i use these gradient styles. I mean whether the browser will take bit more time to render the styles? Because i like it and need to use it . Thats why am asking this?

    Finally i like your site for the clean and simple design…

    Thanks,
    Sekar VK

  106. Are you “slow banning” IE6 the same site at the same time works great on Firefox but IE6 is keeps spinning the loading icon. If so, its not cool at all. You post some coll web design examples and want to view them in older browsers.

  107. Fabulous! Just what I was looking for.. very neatly explained… Great Work!!

  108. Great post, helped me a lot. However, it doesn’t work well in IE9. The gradient CSS command is creating square colored corners…

  109. you are amaizing i am njust a student for webmaster and graphic designer in the print….. and you help me so very much(sorry for the eanglish i am from israel)
    just had to say it:)

  110. Hi thanks for sharing this.. but there is a problem with radius in IE 9. gradient is visible outside radius. Any solution for that?

    1. If you move the final border-radius css rule to the bottom of .button then it works in IE9 as well. Guess it’s just the order in which they appear screws up IE…

  111. CORRECTION (it took my anchor tag and thought I was making a link): Love the buttons, BUT – when using a “button” tag the buttons become taller than with an “a” anchor tag. This problem even shows on the demo page. (FYI: I am using Firefox 5 but have had this problem since Firefox 3.6). Do you know how to fix this as it can be really off-putting when the two are right next to each other (one as a link, the other as part of a form)? Other than this small problem, these buttons are so cool and slick!

  112. Hi all,

    Love the buttons, but as stated unfortunately IE9 is played up. For anyone who’s interested, I added this in the .button class to remove the corners for IE – at least you don’t end up with dodgy corners.


    border-radius: .5em;
    <!–

    1. Urgh, that didn’t format right.

      Replace # with !.

      border-radius: .5em;<#–

      1. LOL, that doesn’t output either!

        Last attempt, otherwise just delete the comments.

        Reaplce LB with and # with !.

        LB#–[if #IE]RBLB#–RBborder-radius: .5em;LB#–LB#[endif]–RB

  113. Screw it, just use the if !IE syntax in CSS and remove the border-radius in the .button class.

  114. Hey there! I know you posted this over a year ago, but wow wow wow! I have been trying to update the various “button” type elements on our website and the amount of CSS I used was atrocious. Not to mention in Internet Exploder it looked like just a text link with space around it. Thank you for this post!

    Oh, and I’m thoroughly impressed. At least two (2) posts on each page of your blog has something I can use. You rock!

  115. Hello really Good Work am get & unique idea Your Best Tutorials Thx and GOD BLESS YOU Get Complete Tutorials
    on my website SEO Tips & web design Tips in Photoshop

  116. thanks much, I’ve been scouring the web for three days and had a few usable but clumsy solutions. This is great stuff, gonna make me look like I know what I am doing. Too many tutorials show things that don’t work that well or that you need to spend a lot of time refining. This works great right out of the box. thanks!

  117. I’m doing some buttons and wanted them to have a gradient background, and found your post really useful so thanks

  118. You can support even more browsers if you add their vendor-specific gradients:

    background: -webkit-gradient(linear, left top, left bottom, from(#444), to(#000));
    background: -webkit-linear-gradient(top, #444, #000);
    background: -moz-linear-gradient(top, #444, #000);
    background: -ms-linear-gradient(top, #444, #000);
    background: -o-linear-gradient(top, #444, #000);
    background: linear-gradient(top, #444, #000);

  119. Also do NOT use RGBA() with text-shadow – unless you want your elements to look hideous in Chrome… just use a hex code for text-shadow instead.

  120. i got to you blog a project late, but still best thing i have see, just copy and past stuff no need to do any thing with out images. thanks a lot for sharing

  121. IMPORTANT: I just made this discovery last week, but if you put a space (yes, a space) AFTER “progid:” you will get beautiful buttons in IE9 as well!!!

    filter: progid: DXImageTransform[…]

    Cheers

    1. Unfortunately your just voiding the gradient and it’s falling back to the solid color… so your still not getting both border radius and background gradient.

    2. I definitely misspoke in this comment – my apologies to everyone in this thread – I should have come back and corrected myself once I discovered my error. You cannot use rounded corners and background gradients in IE9. You can sometimes use an inset shadow to achieve a similar effect.

    1. Unfortunately, it appears to work on small buttons, but you will notice that the entire gradient gets defeated. Try doing it on a large DIV… you will see that there is no gradient at all.

      The rule of thumb I now live by is to not use filter: progid:… with rounded corners… ever. Sometimes I will use an inset shadow to give the effect I am after.

      So sad that IE9 supports background gradients and rounded corners… but not both :(

  122. Hi,

    Anyhow it is not working on IE9 as IE9 does not support gradient effect and border-radius properties at the same time!
    I am working on this issue. I will get back to you if I found something.

    regards,
    Siddhesh

      1. The solution was alreadey in the comments:

        IMPORTANT: I just made this discovery last week, but if you put a space (yes, a space) AFTER “progid:” you will get beautiful buttons in IE9 as well!!!

        filter: progid: DXImageTransform[…]

        Cheers

  123. Hi, thanks for the code.

    Just created a plugin for wordpress to add your css buttons to wordpress websites. Need your mail to send you the zip file.

    cheers,
    serge

  124. Thanks for this information! At the same time I searched for this, I also came across CSS Pie () which addresses the extra features for Internet Explorer. I think I will try your solution first though, because I like understanding what goes on underneath. (And thanks for the people in the comments for pointing out the IE9 fix).

  125. Hi, I know you answered some questions about the IE problem, but I still cannot get this to work on IE. I am quite a newbie about CSS, so if you excuse my maybe silly question: I added the space like this: filter:progid: DXImageTransform.Microsoft.gradient(startColorstr=’#ed1c24′, endColorstr=’#aa1317′);

    But IE still doesn;t show the color of the button, it’s just rectangular and white

  126. Great Stuff! Thanks so much as I was having problems with CSS3 buttons in IE8, but this fixed it. However, I did have a question as I’m a newbie to all this stuff. I have a lightbox that pops out once a button is clicked. Here’s the code using my previous button: Button

    I can’t get the lightbox to work with this button’s code because it uses the “class=” as well. Is there a work around or simple line of code that I’m missing? Any help would be appreciated!

  127. Hi! i’m using thesis theme and i don’t know where to put the code you gave is it for custom.css? please reply!

  128. Hi Nick,

    Love the buttons! I have a question: given a base color, how do you get the rest of the colors (border-color, gradient colors, etc)? Are the rest of the colors the base color with the brightness changed?

    -Venkat

  129. these are really good by Anas Ali
    web designer at
    Ahead

    Creations web designing company in delhi India

  130. freelance web designer and developer from Malaysia | cheap Web Designing.Offers bunch of creative services – custom web design (layout, applications, CMS, CSS), e-commerce, graphic design, creative copywriting, corporate identity, logo design,interactive marketing and more…

  131. Hi,
    I have a question i have the code but when i put it into my css how do i name it so i can link it to a div in html.
    Like normally i would do for example in my css — “#navagation { (and then the code);}”
    and i would link it to my html code like for example–” “

  132. Hi,
    I Really like this code! :) but
    I have a question i have the code but when i put it into my css how do i name it so i can link it to a div in html.
    Like normally i would do for example in my css — “#navagation { (and then the code);}”
    and i would link it to my html code like for example–” “

  133. Hey, this is probably the best CSS3 button tutorial I’ve seen in a while. Good work!

  134. Very nice buttons, although in IE9 the gradient goes right up to the corners yet there is a rounded border radius inside of that which looks a little untidy…. i’m trying to fix it, any ideas ?

    1. Me too, looks ok with the colours I’m using but would be ideal to get them rounded!

  135. Wonderful, thank you. Looks great in Firefox, in Chrome and IE, don’t care about gradient or corners, just have to get the text fitting in the “Add to Cart” buttons, don’t know how that got so wonky.
    Working on site here –

    1. Hi Tammy
      Just been to your site to see your buttons and was impressed :-)
      How do you get that lovely central bit that separates from the background?
      I’d love to do that to some of my pages [just learning web design]
      Many thanks

  136. Nice example on creating a very catchy CSS 3 button. I was working on a project and needed something cool to make it stand out and your examples here have definitely saved the day.

    Great job.

  137. A very good tutorial. Another thing to note, if anyone is interested, is that you can add multiple box-shadow’s. You can use the inset feature to give a good internal shadow, or give your buttons a bevel effect. Something like this:
    -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.2),inset 1px 1px 1px rgba(255,255,255,.3);
    -moz-box-shadow: 0 1px 2px rgba(0,0,0,.2),inset 1px 1px 1px rgba(255,255,255,.3);
    box-shadow: 0 1px 2px rgba(0,0,0,.2),inset 1px 1px 1px rgba(255,255,255,.3);

    And regarding the IE gradient, it overlaps the border radius. So definitely impractical to use on elements with rounded borders. Maybe there’s a fix somwhere :D

  138. Hi, Can I use this code to use as an input button for a form?
    If so, How would I add this to the code below.

    Thanks

  139. is there a way to embed a picture inside the button instead of text?

  140. Thank you very much, easily one of the best tutorials on CSS buttons out there!

  141. Lots of code for something so simple. I still think images are faster than writing all that code. Hopefully they will make the css easier to write in the future.

    1. Um…no, they are not faster…and, not a lot of code…why use an image when you can use CSS3?

  142. Do you have any fix for rounded corners and gradient filter edges bleeding in IE9? I’ve tried adding a containing element and setting the overflow to hidden, but it doesn’t correctly apply a mask to the buttons.

  143. Wow!!! Great Css, i was looking for it. I will use it in my current website.

    thanks.

  144. I’m taking a web course this semester and would LOVE to go to this conference to learn more. I’m totally inspired by Sarah Parmenter’s website and I really would appreciate going to hear her talk

  145. Thanks. I’m looking for CSS buttons that draw with a glass look to them. Any suggestions?

  146. IE 9 fix change border radius width to 8px rather than 0.5em

    1. -webkit-border-radius: 8px;
      -moz-border-radius: 8px;
      border-radius: 8px;

      Like that ? It does’nt work…

  147. Really a best website for the web designers. I am really interested to create css button..So i will learn it

  148. Thanks for your help, trying hard, here is a yellow code for people to try out:

    /* Yellow */
    .yellow {
    color: #010101;
    border: solid 1px #E7EE61;
    background: #F1FD03;
    background: -webkit-gradient(linear, left top, left bottom, from(#D2D20F), to(#EFF372));
    background: -moz-linear-gradient(top, #D2D20F, #EFF372);
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=’#D2D20F’, endColorstr=’#EFF372′);
    }
    .yellow:hover {
    background: #D1DA1A;
    background: -webkit-gradient(linear, left top, left bottom, from(#F6F5AC), to(#D2CB33));
    background: -moz-linear-gradient(top, #F6F5AC, #D2CB33);
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=’#F6F5AC’, endColorstr=’#D2CB33′);
    }
    .yellow:active {
    color: #EFF75E;
    background: -webkit-gradient(linear, left top, left bottom, from(#EFF372), to(#FFFFB3));
    background: -moz-linear-gradient(top, #EFF372, #FFFFB3);
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=’#EFF372′, endColorstr=’#FFFFB3′);
    }

    Regards

  149. Beautiful! Thanks!
    Only one different button color in my ul/li does not work, how do i solve that…

  150. My problem ist, that I have a standard a:link with dark blue color definition that I want to override within these buttons, but it does not work. I would like to have white or yellow as link color in .blue, but until now nothing seemed to work and I got some strange results. Could you change the code so that color codes that are given in .color override the set general link color?

    Regards
    Thomas

  151. it does not look the same in IE (6.0+) , round corners are lost and there is no gradient color as it may appear in Chrome and Firefox … !!!

Comments are closed on this article.