Simple HTML and CSS button designs

Buttons are something that comes up constantly in web production, yet examples on the web still mix old and new techniques. Many articles continue to rely on outdated approaches, which made this feel like a useful topic to revisit.

This article introduces 11 HTML and CSS button designs built around a simple idea: buttons that use current techniques and are easy to use in real projects.

The buttons in this article share a few characteristics:

  • Mostly shown with <a> elements, but easy to adapt to <button> elements when appropriate
  • Built with only HTML and CSS, without JavaScript
  • Easy to copy and paste

A later Column summarizes the implementation points that were kept in mind across these examples. All demos in this article are original and are published on GitHub under the MIT License.

Note: The hover effects in these demos are only visible on devices where hover is available. This is covered later in the Column.

Outline button with a gradient border

This button recreates the outline with a gradient and uses the same gradient to create a glow on hover.

A normal outline might suggest using the border property, but linear-gradient() cannot be used there. Instead of relying on border, the same gradient is layered on the parent element and a pseudo-element to recreate the outline.

The glow on hover is created by applying the same gradient to a pseudo-element, blurring it with filter: blur(), and fading it in.

Excerpt from the hover effect implementation:

/* Button styles */
.button-outline-gradient {
  --button-color: #fff;
  --button-surface: #222;
  --button-gradient-start: #3223b3;
  --button-gradient-end: #7a70dd;

  /* Omitted */

  background: linear-gradient(
    135deg,
    var(--button-gradient-start),
    var(--button-gradient-end)
  );
}
.button-outline-gradient::before {
  /* Omitted */

  background: linear-gradient(
    135deg,
    var(--button-gradient-start),
    var(--button-gradient-end)
  );
  filter: blur(8px);
  border-radius: inherit;
  opacity: 0;
}

@media (any-hover: hover) {
  .button-outline-gradient::before {
    transition: opacity 0.2s;
  }

  .button-outline-gradient:hover::before {
    opacity: 1;
  }
}

For a more detailed explanation of CSS gradients, see the following Japanese-language article.

Outline button with a glossy hover effect

The glossy outline is recreated with conic-gradient(). A rotating highlight is layered on .button-glow, and the effect uses @property, an at-rule for defining custom properties, to rotate the highlight inside the conic-gradient().

Excerpt from the hover effect implementation:

/*
 * Define a CSS variable that stores an angle value
 * Browser support: https://caniuse.com/mdn-css_at-rules_property
 */
@property --angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.button-outline-glow {
  --button-color: #fff;
  --button-surface: #222;
  --button-outline: #7a70dd;

  .button-glow {
    background: conic-gradient(
      from var(--angle),
      var(--button-surface),
      var(--button-outline),
      var(--button-surface),
      var(--button-outline),
      var(--button-surface)
    );
    animation: gradient-spin 3s linear infinite;
  }
}

/* Animate the CSS variable with @keyframes */
@keyframes gradient-spin {
  from {
    --angle: 0deg;
  }

  to {
    --angle: 360deg;
  }
}

@property is available in Chrome and Edge 85 and later (August 2020), Safari 16.4 (March 2023), and Firefox 128 and later (July 2024).

Neumorphic button

This button recreates a neumorphic look by layering multiple box-shadow values. The button is circular, the shadows become shallower on hover, and an inset shadow is used on press to make it feel pushed in.

Excerpt from the box-shadow implementation:

.button-neumorphic {
  --button-color: #222;
  --button-surface: #f5f5f5;
  --button-shadow-highlight: #fff;

  /* Omitted */

  background-color: var(--button-surface);
  border-radius: 50%;
  box-shadow:
    -4px -4px 8px var(--button-shadow-highlight),
    4px 4px 8px rgb(0 0 0 / 24%);
}

For a deeper look at box-shadow, see the following article.

Text button with a sliding underline on hover

This text button slides the underline in from the left on hover, then slides it out to the right when the pointer leaves. It is a common pattern in horizontal menus.

The underline effect is built with a pseudo-element using the background and transform properties. The implementation follows two main ideas:

  • Make the hover-out animation finish faster than the hover-in animation
  • Adjust the easing so the hover-out animation still feels natural

For more on the easing used here, see the following article.

If you want buttons and other small UI elements to feel a bit more refined, Using CSS linear() for spring animations in UI is also worth reading. It covers the idea behind spring animation with linear() and shows how it can be applied to buttons, toggle switches, tabs, and more.

Excerpt from the hover effect implementation:

.button-underline {
  --button-color: #222;
  --button-underline-color: #3223b3;
}

.button-underline::after {
  /* Omitted */

  width: 100%;
  background-color: var(--button-underline-color);
  transform: scaleX(0);
  transform-origin: right top;
}

@media (any-hover: hover) {
  .button-underline::after {
    /* The easing uses easeOutExpo (https://ics.media/en/entry/18730/) */
    transition: transform 0.2s cubic-bezier(0.19, 1, 0.22, 1);
  }

  .button-underline:hover::after {
    /* The easing uses easeInOutExpo (https://ics.media/en/entry/18730/) */
    transition: transform 0.4s cubic-bezier(0.9, 0, 0, 1);
    transform: scaleX(1);
    transform-origin: left top;
  }
}

When you copy and paste this pattern, keep in mind that the feel of the animation changes depending on the width of the button, so it usually needs to be adjusted case by case.

If you need even finer control, JavaScript is one option.

If you also want to apply a similar effect to the text color, see the following Japanese-language article.

Button with an icon and text side by side

This button places a circular icon at one end of the button, with text next to it.

Which side the icon appears on is controlled by the presence or absence of the .is-reverse class.

Excerpt showing .is-reverse:

<a href="#" class="button-icon-badge">
  <!-- Omitted -->
</a>
<a href="#" class="button-icon-badge is-reverse">
  <!-- Omitted -->
</a>
.button-icon-badge {
  --button-color: #fff;
  --button-background: #3223b3;
  --button-background-hover: #281c8f;

  /* Omitted */

  &.is-reverse {
    flex-direction: row-reverse;
    padding: 8px 8px 8px 40px;
  }
}

The icons are built with inline SVG.

Excerpt from the HTML:

<a href="#" class="button-icon-badge">
  <span class="icon-box">
    <svg viewBox="0 0 24 24">
      <rect x="3.5" y="5.5" width="17" height="12" rx="1.5" />
      <path d="M6 9C6 9 11.5 12 12 12C12.5 12 18 9 18 9" />
    </svg>
  </span>
  <span class="label">Contact</span>
</a>

Variation: arrow icon

This is another side-by-side variation, adjusted for arrow icons. It also adds a small sliding motion to the arrow on hover.

Variation: centered icon and text

This is another side-by-side variation, with the icon and text centered together.

Circular icon-only button

From here, the remaining examples are even more minimal.

This is a simple circular button made only of an icon. In addition to the mail icon, the demo also includes arrow variations pointing up, down, left, and right.

One thing to note is that the outer <a> element uses a placeholder aria-label attribute. If you copy and paste this pattern, make sure to update it as needed.

Excerpt from the HTML:

<a href="#" class="button-icon-round" aria-label="Contact">
  <!-- Omitted -->
</a>

Outline button

This is a simple button with a transparent interior and a colored outline.

Filled pill button

This is a simple button with a pill shape created by adjusting the corners with border-radius.

The shape is created by setting border-radius to half the button height.

Excerpt showing the pill shape:

.button-solid-pill {
  /* Omitted */

  height: 64px;
  border-radius: 32px; /* Half the button height */
}

Filled rectangular button

This is the simplest rectangular button of all.

Column: Implementation tips

This section briefly covers the points that were kept in mind across all of the buttons. Depending on the project and development environment, some of these choices may not be appropriate as-is, so adjust the CSS as needed before copying it into production.

Reset built-in browser styles

To keep the examples simple, most are implemented with <a> elements. If you switch them to <button> elements when appropriate, it helps to add reset CSS so browser differences are easier to manage. If this overlaps with code already in your project, remove it as needed.

/* Reset CSS for <a> elements (remove the selector if not needed) */
a {
  color: inherit;
  text-decoration: none;
}

/* Reset CSS for <button> elements (remove the selector if not needed) */
button {
  padding: 0;
  font-family: inherit;
  appearance: none;
  cursor: pointer;
  background-color: transparent;
  border: none;
}

Usability considerations when switching to a <button> element

These demos mainly use <a> elements, but when a <button> element is tapped on a touch device, text can sometimes be selected unintentionally in addition to the button reacting. If you switch to <button>, adding user-select: none helps prevent that. Remove it if you do not need it.

As a side note, Safari still requires the vendor-prefixed -webkit-user-select property, so both are included here.

.button {
  /* Omitted */

  /* Applied to all button classes in these examples. Remove if unnecessary. */
  -webkit-user-select: none;
  user-select: none;
}

Another property not used in these demos is -webkit-touch-callout.

In iOS Safari, -webkit-touch-callout controls the menu shown when the user long-presses an element. That menu can preview the destination page or open it in another tab. If an <a> element is being used for something like toggling a hamburger menu rather than actual navigation, that long-press menu can cause unintended behavior. In that case, -webkit-touch-callout: none can be used to suppress it.

Apply hover effects only on devices that support hover

On touch devices that do not support hover, touching the element can still trigger a hover state. To avoid that, these examples apply hover effects only when @media (any-hover: hover) matches.

.button {
  --button-background: #3223b3;
  --button-background-hover: #281c8f;

  background-color: var(--button-background);
}
/*
 * Apply hover effects only on devices that support hover.
 * If you do not need device detection, remove `@media (any-hover: hover) {}`.
 */
@media (any-hover: hover) {
  .button {
    transition: background-color 0.2s;
  }
  .button:hover {
    background-color: var(--button-background-hover);
  }
}

The any-hover media feature is also covered in the following article.

Conclusion

This article introduced simple, easy-to-use buttons built with HTML and CSS. Hopefully the implementation details and design considerations behind each example will be useful in your own work.

As mentioned throughout the article, ICS MEDIA also covers several of these techniques in more detail in the following articles.

Share on social media
Your shares help us keep the site running.
Post on X
Share
Copy URL