Form Element Styling

Chapter 13: Form Element Styling

Forms are the primary way users interact with your application. However, default browser styles for forms are often inconsistent and outdated. This chapter teaches you how to take full control over inputs, buttons, selects, and text areas to create a cohesive and professional user experience.


1. Resetting the Defaults

Most form elements come with "user-agent styles" (browser defaults) that include bulky borders, specific backgrounds, and strange internal spacing. The first step is often a "clean slate" using appearance: none to remove OS-specific styling.

button, input, select, textarea {
  font-family: inherit; /* Use the site's font */
  font-size: 100%;      /* Prevent scaling issues on mobile */
  margin: 0;            /* Remove default margins */
  box-sizing: border-box;
  appearance: none;     /* Removes default browser styling */
}

2. Styling Text Inputs and Textareas

Modern inputs should feel tactile and responsive to focus. A common technique is to use a subtle transition on the border color and a soft box-shadow to indicate focus.

.form-input {
  width: 100%;
  padding: 0.75rem 1rem;
  border: 2px solid #e2e8f0;
  border-radius: 0.75rem;
  background-color: #fff;
  color: #1e293b;
  transition: all 0.2s ease;
}

.form-input:focus {
  outline: none;
  border-color: #3b82f6;
  box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
}

.form-input::placeholder {
  color: #94a3b8;
}
Placeholder...Default StateTyping...Focus State (with glow)

3. Customizing the Select Dropdown

The standard <select> is notoriously hard to style. The modern approach is to hide the default arrow using appearance: none and provide your own using a background image.

.custom-select {
  appearance: none;
  padding: 0.75rem 2.5rem 0.75rem 1rem;
  background-color: #fff;
  border: 2px solid #e2e8f0;
  border-radius: 0.75rem;
  /* Custom Arrow via Data URI or SVG file */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2364748b'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1rem center;
  background-size: 1.25rem;
  cursor: pointer;
}
Choose an optionCustom SVG Arrow injected via Background-Image

4. Checkboxes and Radios: The "Hidden Input" Pattern

Browser defaults for checkboxes and radios are often small and cannot be easily themed. The solution is to hide the actual input and style a sibling element (usually a span or div) using the :checked pseudo-class.

Checkbox Implementation

/* 1. Hide the real input */
.checkbox-hidden {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

/* 2. Style the custom box */
.checkbox-box {
  display: inline-block;
  width: 1.5rem;
  height: 1.5rem;
  border: 2px solid #cbd5e1;
  border-radius: 0.375rem;
  background: #fff;
  transition: all 0.2s;
}

/* 3. Style the 'Checked' state */
.checkbox-hidden:checked + .checkbox-box {
  background-color: #3b82f6;
  border-color: #3b82f6;
  /* Use a checkmark background or pseudo-element */
}

Radio Implementation

.radio-circle {
  border-radius: 50%; /* Radios are traditionally circles */
}

.radio-hidden:checked + .radio-circle {
  box-shadow: inset 0 0 0 4px #fff; /* Creates the inner dot effect */
  background-color: #3b82f6;
}
UncheckedChecked BoxUnselected RadioSelected Radio

5. Styling Buttons

Buttons need clear hierarchy (primary vs. secondary) and obvious hover/active states.

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.625rem 1.25rem;
  font-weight: 600;
  border-radius: 0.375rem;
  cursor: pointer;
  transition: all 0.2s;
}

.btn-primary {
  background-color: #2563eb;
  color: #fff;
  border: none;
}

.btn-primary:hover {
  background-color: #1d4ed8;
}

.btn-primary:active {
  transform: scale(0.98);
}
DefaultHoverActive

6. Layout: Labeling and Accessibility

Always pair inputs with <label> tags. Use display: flex or grid to create clean form rows. A good practice is to stack labels above inputs on mobile but place them side-by-side on larger screens.

.form-group {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.form-label {
  font-size: 0.875rem;
  font-weight: 600;
  color: #475569;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

7. Debugging Checklist

  1. Are inputs too small on iOS? (Ensure font-size is at least 16px to prevent auto-zoom).
  2. Is the focus ring visible? (Never remove outline without providing a clear :focus style).
  3. Do buttons have a cursor: pointer?
  4. Is the text contrast high enough for readability?
  5. Are labels properly linked to inputs using the for and id attributes?