How to Style a To-Do List with Only CSS (No JavaScript)
Posted Date: 2025-10-28
In the last tutorial, we built a perfectly semantic to-do list using only HTML. It's functional, but it looks a bit plain. What if we want to make it look great and add interactive states, like a strikethrough on completed items? You don't need JavaScript for that. This tutorial will show you how to style your entire to-do list using only CSS.
We'll use the power of CSS pseudo-selectors like :checked and pseudo-elements like ::before to create a beautiful, custom, and interactive list.
The Starting Point: Our HTML
First, we need our clean, semantic HTML. This is the same structure from our previous HTML-only tutorial. The CSS we write will target these specific tags.
<form>
<fieldset>
<legend>My Daily Tasks</legend>
<div class="task-item">
<input type="checkbox" id="task1" name="task1">
<label for="task1">Finish this CSS tutorial</label>
</div>
<div class="task-item">
<input type="checkbox" id="task2" name="task2">
<label for="task2">Learn about the :checked selector</label>
</div>
<div class="task-item">
<input type="checkbox" id="task3" name="task3">
<label for="task3">Build a custom checkbox</label>
</div>
</fieldset>
</form>
Part 1: Basic List Styling
Let's start by styling the container. We'll style the <fieldset>, <legend>, and each .task-item div to give our list a clean, modern look.
body {
background-color: #111827;
color: #E5E7EB;
font-family: sans-serif;
display: grid;
place-items: center;
min-height: 100vh;
}
form {
width: 90%;
max-width: 400px;
}
fieldset {
border: 2px solid #374151;
border-radius: 8px;
padding: 20px;
}
legend {
font-size: 1.5rem;
font-weight: bold;
padding: 0 10px;
color: #F9FAFB;
}
.task-item {
display: block;
margin-bottom: 12px;
position: relative;
}
label {
font-size: 1.125rem;
line-height: 1.5;
cursor: pointer;
}
Part 2: The "Magic" - Styling the Checked State
This is the key to our CSS-only interactivity. We can use the :checked pseudo-class to detect when the checkbox is clicked. Then, using the adjacent sibling selector (+), we can apply styles to the <label> that comes right after it.
input[type="checkbox"]:checked + label {
text-decoration: line-through;
color: #6B7280;
}
Just like that, clicking the checkbox will now add a strikethrough to the text, with no JavaScript involved!
(Advanced) Part 3: Creating Custom Checkboxes
The default browser checkboxes are small and ugly. Let's hide them and create our own custom-styled checkbox using pseudo-elements.
/* 1. Hide the original checkbox */
input[type="checkbox"] {
opacity: 0;
position: absolute;
width: 0;
height: 0;
}
/* 2. Adjust label padding to make space for our box */
label {
padding-left: 35px; /* 25px box + 10px margin */
}
/* 3. Create the custom box using ::before */
label::before {
content: '';
position: absolute;
left: 0;
top: 2px;
width: 22px;
height: 22px;
border: 2px solid #6B7280;
border-radius: 4px;
background-color: #1F2937;
transition: all 0.2s ease;
}
/* 4. Style the box when the (hidden) input is checked */
input[type="checkbox"]:checked + label::before {
background-color: #3B82F6;
border-color: #3B82F6;
}
/* 5. Create the checkmark using ::after */
label::after {
content: '';
position: absolute;
left: 9px;
top: 6px;
width: 6px;
height: 12px;
border: solid white;
border-width: 0 3px 3px 0;
transform: rotate(45deg);
opacity: 0;
transition: opacity 0.2s ease;
}
/* 6. Show the checkmark when the input is checked */
input[type="checkbox"]:checked + label::after {
opacity: 1;
}
/* 7. Add a focus state for accessibility */
input[type="checkbox"]:focus + label::before {
box-shadow: 0 0 0 3px #3B82F680;
}
Conclusion: The Power of Pure CSS
You have now built a beautiful, interactive to-do list using only HTML and CSS. You've learned how to use the :checked selector and sibling combinators to create interactivity, and how to build custom form elements from scratch.
This is the power of modern CSS. Before you reach for JavaScript, always ask: "Can I do this with CSS alone?" Often, the answer is yes.