Skip to content

Styling Buttons and Select Dropdowns

Buttons and select dropdowns require a different approach from text inputs. Buttons need a clean reset before you build their appearance back up. Selects have a hard ceiling on what CSS can control — the visible box is styleable, but the dropdown list itself is not. This lesson covers both.

Before you apply any CSS, a <button> element has:

  • A gray background from the OS theme
  • A border (style varies by browser)
  • The browser’s default font (not your page font)
  • cursor: default — the arrow cursor, not the pointer

The .btn class in the STO stylesheet will handle the visual appearance, but first you need a clean foundation.

button {
cursor: pointer;
border: none;
background: none;
font: inherit;
padding: 0;
}

This removes all browser defaults and gives you a blank canvas. Each property matters:

  • cursor: pointer — changes the cursor to the hand icon, signaling the element is clickable
  • border: none — removes the browser’s default border
  • background: none — removes the default gray background
  • font: inherit — ensures the button uses your page font (same as inputs)
  • padding: 0 — removes default padding so your custom padding is the only padding

The STO site uses a .btn base class with .btn-primary for the green filled style:

.btn {
display: inline-block;
padding: 0.75rem 1.75rem;
border-radius: 4px;
font-weight: 700;
font-size: 1rem;
text-decoration: none;
cursor: pointer;
border: none;
transition: transform 150ms ease, box-shadow 150ms ease;
}
.btn-primary {
background-color: #2c4a1e;
color: #ffffff;
}
.btn-primary:hover {
background-color: #3a5c26;
color: #ffffff;
}

display: inline-block allows the button to have padding and behave as a block for sizing while still flowing inline with text. This is also how <a> elements used as buttons behave — both <button> and <a class="btn"> receive the same visual treatment.

Three interactive states matter for buttons:

.btn-primary:hover {
background-color: #3a5c26;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
.btn-primary:active {
transform: translateY(0);
box-shadow: none;
}
.btn:disabled,
.btn[disabled] {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
  • :hover — lifts the button and deepens the color, signaling interactivity
  • :active — returns the button to its resting position while pressed, giving a tactile “press” feel
  • :disabled — reduces opacity and changes the cursor to not-allowed; pointer-events: none prevents click events from reaching the button

Buttons need a focus ring just like inputs:

.btn:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(44, 74, 30, 0.3);
}

Keyboard users tab to buttons — the focus ring must be visible.

The <select> element has two parts:

  1. The visible box — the closed state showing the currently selected option; fully styleable
  2. The dropdown list — the options that appear when clicked; rendered by the OS and not styleable with CSS

You can make the visible box match your design perfectly. The open dropdown will always look like an OS-native list.

The select box takes the same padding, border, and background as inputs:

.form-group select {
width: 100%;
padding: 0.75rem 1rem;
border: 1px solid #d8d2c8;
border-radius: 4px;
background-color: #ffffff;
color: #1a1a1a;
font: inherit;
font-size: 1rem;
transition: border-color 150ms ease, box-shadow 150ms ease;
}

This makes it visually consistent with the text inputs from Lesson 02.

The browser’s native dropdown arrow is part of the OS rendering. To replace it, first strip the native appearance with appearance: none, then add a custom arrow as a background image:

.form-group select {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%235a5a5a' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 1rem center;
padding-right: 2.5rem;
}

The data:image/svg+xml value is an inline SVG encoded directly in the CSS — a small chevron arrow in #5a5a5a (mid-gray). background-position: right 1rem center places it 1rem from the right edge, vertically centered. padding-right: 2.5rem prevents the user’s selected text from running underneath the arrow.

appearance: none is well-supported in all modern browsers. You can add -webkit-appearance: none for older Safari compatibility.

Checkbox and radio inputs are the most restricted form elements. appearance: none removes their native rendering, leaving a plain box, but rebuilding a fully accessible custom checkbox or radio requires additional HTML and CSS beyond this course’s scope.

For the STO contact form, the exercises use text inputs, email inputs, a select, a textarea, and a submit button — all of which are fully styleable with the techniques in this module.

Style the STO form button and select:

  1. Add the button reset and .btn/.btn-primary rules to style.css:
button {
cursor: pointer;
border: none;
background: none;
font: inherit;
padding: 0;
}
.btn {
display: inline-block;
padding: 0.75rem 1.75rem;
border-radius: 4px;
font-weight: 700;
font-size: 1rem;
text-decoration: none;
cursor: pointer;
border: none;
transition: transform 150ms ease, box-shadow 150ms ease;
}
.btn-primary {
background-color: #2c4a1e;
color: #ffffff;
}
.btn-primary:hover {
background-color: #3a5c26;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
.btn-primary:active {
transform: translateY(0);
box-shadow: none;
}
  1. Open contact.html. Hover the “Send Message” button — it should shift green, lift slightly, and animate smoothly. Click and hold — it should return to rest while pressed.

  2. Add the select styles. Open the page and click the “Interested in” dropdown — the visible box should match the text inputs. The dropdown list itself will still look OS-native, which is expected.

  3. Tab to the submit button with your keyboard. Confirm the focus ring appears.

  4. In DevTools, add the disabled attribute to the button: in the Elements panel, double-click the <button> element and type disabled. The button should fade to 50% opacity and the cursor should change to not-allowed.

  • The button reset (cursor: pointer; border: none; background: none; font: inherit; padding: 0) strips browser defaults before you build a custom appearance.
  • cursor: pointer signals clickability; always add it to buttons.
  • :hover, :active, and :disabled are the three button states to style. :active gives tactile press feedback; :disabled makes unavailable buttons visually clear.
  • Buttons and <a> elements used as buttons receive the same .btn class — display: inline-block makes padding and sizing behave consistently for both.
  • appearance: none on <select> removes the native dropdown arrow; add a custom SVG arrow via background-image and adjust padding-right to prevent text overlap.
  • The select dropdown list (the open state) is OS-rendered and cannot be styled with CSS.

Lesson 04 uses Flexbox to control how labels, inputs, and buttons are arranged in the form layout.