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.
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-typepicks the marker:disc,circle,square,decimal(1, 2, 3),lower-alpha(a, b, c),upper-roman(I, II, III), ornone.list-style-position: insidetucks the bullet into the text block.list-style: noneremoves 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: flexon 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;
}
Styling links
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-decorationcontrols the underline (none,underline,line-through).text-underline-offsetmoves 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-visiblestyles matter for keyboard visitors. Never hide the outline without a replacement.cursor: pointeris the default on links, so you only need it on non-link clickable things.
A link that looks like a button
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: inheritfixes the classic problem of form controls ignoring your page font.box-sizing: border-boxkeepswidth: 100%from overflowing the form (see the reset lesson). If you already have the global reset, you can leave this line out.- The
:focusstyle 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 bothlist-styleandpadding. - Putting the padding on
<li>instead of the link, so only the text is clickable, not the whole button-like area. - Forgetting
display: blockorinline-blockon links when you want padding, width or height to apply. - Styling
a:hoverbut 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 withoutbox-sizing: border-box. - Form controls in a different font, because
font: inheritwas 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
- Click Practice in Editor. Turn the
.nav-listinto a horizontal menu and mark.currentwith a blue background. - Give the
.stepslist square bullets, blue markers and space between items. - Turn the “Get started” link into a green button and give it a hover colour.
- Style
.form-inputso 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. - Create
.btn-primaryand.btn-secondary, then add a:disabledstyle and test it by adding thedisabledattribute to one button. - Make the checkbox blue with
accent-color, and make the whole label clickable. - Stop the textarea from being resized sideways.
Recap
- Reset lists with
list-style: none; margin: 0; padding: 0;, make itemsinline-block, and give linksdisplay: blockplus padding to build a menu. - Style markers with
list-style-typeand the::markerpseudo-element. - Style links for all states, including
:focus-visible, and keep them recognisable. - Build buttons with a shared
.btnclass and small variants such as.btn-primary. Usefont: inheritandcursor: pointer. - Give form controls one shared class,
font: inherit,width: 100%withbox-sizing: border-box, and a visible focus style. - Use
accent-colorfor checkboxes and radios,resize: verticalfor textareas, and be careful with:invalid. - Next you will learn
position: stickyand classic page-layout patterns.