CSSIntermediate

Styling Lists, Links, Buttons and Forms with CSS

Turn plain lists into menus, style link states and buttons, and build a clean, accessible form with inputs, select, textarea and checkboxes.

All CSS lessons

What you will learn

Lists, links and forms are the three most common pieces of interface on the web. Browsers give them plain default styles, so almost every real site restyles them. In this lesson you will learn how to style lists (bullets, numbers and menus), links and their states, buttons, and form controls: text fields, selects, textareas and checkboxes. As always, you will see exactly which class sits on which element.

The HTML we will style

<header class="site-header">
  <h1 class="logo">CodeNest</h1>
  <nav>
    <ul class="nav-list">
      <li class="nav-item"><a href="#home" class="nav-link current">Home</a></li>
      <li class="nav-item"><a href="#courses" class="nav-link">Courses</a></li>
      <li class="nav-item"><a href="#tools" class="nav-link">Tools</a></li>
      <li class="nav-item"><a href="#about" class="nav-link">About</a></li>
    </ul>
  </nav>
</header>

<main class="page">
  <h2>Recommended steps</h2>
  <ul class="steps">
    <li>Learn HTML first</li>
    <li>Then learn CSS</li>
    <li>Practise with small projects</li>
  </ul>

  <p>
    Read the <a href="#docs" class="text-link">documentation</a>, or
    <a href="#start" class="btn-link">Get started</a>.
  </p>

  <form class="contact-form">
    <div class="form-group">
      <label for="name" class="form-label">Name</label>
      <input type="text" id="name" class="form-input" placeholder="Your name">
    </div>

    <div class="form-group">
      <label for="topic" class="form-label">Topic</label>
      <select id="topic" class="form-input">
        <option>HTML</option>
        <option>CSS</option>
        <option>JavaScript</option>
      </select>
    </div>

    <div class="form-group">
      <label for="msg" class="form-label">Message</label>
      <textarea id="msg" class="form-input" rows="4" placeholder="Write here..."></textarea>
    </div>

    <label class="form-check">
      <input type="checkbox"> Send me the newsletter
    </label>

    <button type="submit" class="btn btn-primary">Send message</button>
    <button type="reset" class="btn btn-secondary">Clear</button>
  </form>
</main>
Selector Element What it is
.nav-list <ul> in the header the menu list
.nav-item each <li> in the menu one menu entry
.nav-link, .current each <a> in the menu; current is on “Home” the clickable part; current marks the page you are on
.steps the plain <ul> a list that keeps its bullets, restyled
.text-link the <a> in the paragraph an ordinary inline link
.btn-link the “Get started” <a> a link that looks like a button
.contact-form the <form> the whole form
.form-group a <div> around each label and field spacing between fields
.form-label each <label> the field names
.form-input the <input>, <select> and <textarea> one shared class for all three fields
.form-check the checkbox <label> checkbox row
.btn, .btn-primary, .btn-secondary the two <button>s shared button style, plus a variant each

Styling lists

Bullets and numbers: list-style

Browsers add bullets to <ul>, numbers to <ol> and 40px of left padding. You control these with the list-style properties:

/* the <ul class="steps"> */
.steps {
  list-style-type: square;        /* disc (default), circle, square, none */
  list-style-position: outside;   /* outside (default) or inside */
  padding-left: 24px;
}
  • list-style-type picks the marker: disc, circle, square, decimal (1, 2, 3), lower-alpha (a, b, c), upper-roman (I, II, III), or none.
  • list-style-position: inside tucks the bullet into the text block.
  • list-style: none removes bullets altogether, which is the first step to every menu.

To colour or size only the marker, use the ::marker pseudo-element:

.steps li::marker {
  color: royalblue;
  font-size: 1.2em;
}

Give list items some breathing room:

.steps li {
  margin-bottom: 8px;
}

Turning a list into a horizontal menu

Nearly every navigation bar is an <ul>. To turn a vertical list into a horizontal menu, reset the list, lay the items in a row, and make the links big:

/* 1. The <ul class="nav-list">: no bullets, no default spacing */
.nav-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* 2. Each <li class="nav-item">: sits next to the others */
.nav-item {
  display: inline-block;
}

/* 3. The <a class="nav-link">: fills the whole item with a large click area */
.nav-link {
  display: block;
  padding: 10px 16px;
  color: royalblue;
  text-decoration: none;
}

Why display: block on the link? An inline link only has a click area as big as its text, and its vertical padding does not push anything. As a block, the whole padded rectangle is clickable, which makes it much easier to hit on a phone.

A modern shortcut: display: flex on the <ul> makes the same row in one line and gives you alignment and spacing tools. The Flexbox lessons cover it. The technique above works everywhere and is a good way to understand what is happening.

Marking the current page

/* the <a class="nav-link current"> */
.nav-link.current {
  background-color: royalblue;
  color: white;
}

Combine it with the hover state from the pseudo-class lesson:

.nav-link:hover {
  background-color: #dbe4ff;
}

.nav-link.current:hover {
  background-color: royalblue;
}

A link can be in several states. You learned the four pseudo-classes (:link, :visited, :hover, :active) earlier. Here is a practical set:

/* the <a class="text-link"> inside the paragraph */
.text-link {
  color: royalblue;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.text-link:hover {
  color: darkblue;
  text-decoration-thickness: 2px;
}

.text-link:focus-visible {
  outline: 3px solid orange;
  outline-offset: 2px;
}
  • text-decoration controls the underline (none, underline, line-through). text-underline-offset moves it away from the text for a cleaner look.
  • Keep links recognisable. If you remove the underline, make sure colour or weight still makes them stand out, and never depend on colour alone.
  • :focus-visible styles matter for keyboard visitors. Never hide the outline without a replacement.
  • cursor: pointer is the default on links, so you only need it on non-link clickable things.

Any <a> can look like a button by giving it padding and a background. Make it inline-block so the padding works:

/* the <a class="btn-link"> */
.btn-link {
  display: inline-block;
  padding: 10px 20px;
  background-color: seagreen;
  color: white;
  text-decoration: none;
  border-radius: 6px;
}

.btn-link:hover {
  background-color: darkgreen;
}

Use an <a> when the thing goes somewhere (a new page). Use a <button> when it does something (submits a form, opens a menu).

Styling buttons

Buttons have ugly defaults, so most of the work is overriding them. Build them with a shared class and small variants:

/* both <button class="btn ..."> */
.btn {
  padding: 10px 20px;
  border: 2px solid transparent;
  border-radius: 6px;
  font: inherit;            /* use the page's font instead of the system one */
  cursor: pointer;
}

/* the "Send message" button: the main action */
.btn-primary {
  background-color: royalblue;
  color: white;
}

.btn-primary:hover {
  background-color: darkblue;
}

/* the "Clear" button: a quieter outline style */
.btn-secondary {
  background-color: transparent;
  color: royalblue;
  border-color: royalblue;
}

.btn-secondary:hover {
  background-color: #eef2ff;
}

.btn:focus-visible {
  outline: 3px solid orange;
  outline-offset: 2px;
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

Because each button has two classes (btn btn-primary), the shared look lives in one place and each variant adds only what is different. This is how almost all button systems are built.

Styling forms

Labels and spacing

/* the <form class="contact-form"> */
.contact-form {
  max-width: 420px;
}

/* the <div class="form-group"> around each label and field */
.form-group {
  margin-bottom: 16px;
}

/* the <label class="form-label"> */
.form-label {
  display: block;            /* put the label above the field */
  margin-bottom: 6px;
  font-weight: 600;
}

Text fields, select and textarea

All three controls share the class form-input, so one rule styles them together:

/* <input>, <select> and <textarea> with class="form-input" */
.form-input {
  display: block;
  width: 100%;
  padding: 10px 12px;
  border: 2px solid #cccccc;
  border-radius: 6px;
  font: inherit;
  background-color: white;
  box-sizing: border-box;    /* so width: 100% includes padding and border */
}

.form-input:focus {
  border-color: royalblue;
  outline: none;
  box-shadow: 0 0 0 3px rgba(65, 105, 225, 0.25);
}

.form-input::placeholder {
  color: #999999;
}
  • font: inherit fixes the classic problem of form controls ignoring your page font.
  • box-sizing: border-box keeps width: 100% from overflowing the form (see the reset lesson). If you already have the global reset, you can leave this line out.
  • The :focus style replaces the default outline with a visible ring, so keyboard users can still see where they are.

Textarea specifics

textarea.form-input {
  resize: vertical;       /* none, both, horizontal or vertical */
  min-height: 100px;
}

resize: vertical lets the visitor make the box taller but prevents them from breaking your layout by making it wider.

Checkboxes and radios

Browsers draw checkboxes themselves, and you cannot style their inside much. You can, though, change their colour in one line and style the label around them:

/* the <label class="form-check"> */
.form-check {
  display: block;
  margin-bottom: 16px;
  cursor: pointer;
}

.form-check input {
  accent-color: royalblue;   /* the tick colour */
  width: 18px;
  height: 18px;
  margin-right: 8px;
  vertical-align: middle;
}

Because the <input> is inside the <label>, clicking the text ticks the box. That is also good for accessibility, because it makes the click target larger.

Validation states

.form-input:invalid {
  border-color: crimson;
}

Be careful: :invalid can apply before the visitor has typed anything (for example an empty required field), making a fresh form look full of errors. A common approach is to show the error style only after the field has been touched, using JavaScript or the newer :user-invalid pseudo-class where supported.

Putting it together

.nav-list { list-style: none; margin: 0; padding: 0; }
.nav-item { display: inline-block; }
.nav-link {
  display: block;
  padding: 10px 16px;
  color: royalblue;
  text-decoration: none;
}
.nav-link:hover { background-color: #dbe4ff; }
.nav-link.current { background-color: royalblue; color: white; }

.steps { list-style-type: square; padding-left: 24px; }
.steps li { margin-bottom: 8px; }
.steps li::marker { color: royalblue; }

.text-link { color: royalblue; text-underline-offset: 3px; }
.text-link:hover { color: darkblue; }

.btn-link {
  display: inline-block;
  padding: 10px 20px;
  background-color: seagreen;
  color: white;
  text-decoration: none;
  border-radius: 6px;
}

.contact-form { max-width: 420px; }
.form-group { margin-bottom: 16px; }
.form-label { display: block; margin-bottom: 6px; font-weight: 600; }

.form-input {
  display: block;
  width: 100%;
  padding: 10px 12px;
  border: 2px solid #cccccc;
  border-radius: 6px;
  font: inherit;
  box-sizing: border-box;
}
.form-input:focus {
  border-color: royalblue;
  outline: none;
  box-shadow: 0 0 0 3px rgba(65, 105, 225, 0.25);
}

.form-check { display: block; margin-bottom: 16px; }
.form-check input { accent-color: royalblue; }

.btn {
  padding: 10px 20px;
  border: 2px solid transparent;
  border-radius: 6px;
  font: inherit;
  cursor: pointer;
}
.btn-primary { background-color: royalblue; color: white; }
.btn-secondary { background-color: transparent; color: royalblue; border-color: royalblue; }

Common mistakes

  • Removing bullets but leaving the default padding-left: 40px, so the menu is indented. Reset both list-style and padding.
  • Putting the padding on <li> instead of the link, so only the text is clickable, not the whole button-like area.
  • Forgetting display: block or inline-block on links when you want padding, width or height to apply.
  • Styling a:hover but not :focus-visible, leaving keyboard users without feedback.
  • Removing the focus outline with nothing in its place.
  • Fields that overflow the form because width: 100% was used without box-sizing: border-box.
  • Form controls in a different font, because font: inherit was missing.
  • A placeholder used instead of a label. Placeholders disappear when typing and are not a substitute for <label>.
  • Using <a> for actions and <button> for navigation. Choose the element by its meaning, then style it.

Practice

  1. Click Practice in Editor. Turn the .nav-list into a horizontal menu and mark .current with a blue background.
  2. Give the .steps list square bullets, blue markers and space between items.
  3. Turn the “Get started” link into a green button and give it a hover colour.
  4. Style .form-input so that all three controls look the same, with a blue ring on focus. Check that you can still see the focus when you press Tab.
  5. Create .btn-primary and .btn-secondary, then add a :disabled style and test it by adding the disabled attribute to one button.
  6. Make the checkbox blue with accent-color, and make the whole label clickable.
  7. Stop the textarea from being resized sideways.

Recap

  • Reset lists with list-style: none; margin: 0; padding: 0;, make items inline-block, and give links display: block plus padding to build a menu.
  • Style markers with list-style-type and the ::marker pseudo-element.
  • Style links for all states, including :focus-visible, and keep them recognisable.
  • Build buttons with a shared .btn class and small variants such as .btn-primary. Use font: inherit and cursor: pointer.
  • Give form controls one shared class, font: inherit, width: 100% with box-sizing: border-box, and a visible focus style.
  • Use accent-color for checkboxes and radios, resize: vertical for textareas, and be careful with :invalid.
  • Next you will learn position: sticky and classic page-layout patterns.