{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreib2supoysiku4kirdnteqs2627ditey5ew5xgyi63djec2a44tptu",
"uri": "at://did:plc:ygdeyuxj3bfk42zacptp3sw7/app.bsky.feed.post/3mnwwzy6xuf2y"
},
"canonicalUrl": "https://duskyelf.com/blogs/pure-css-todo-app",
"description": "Walkthrough of the techniques used in a fully functional Todo app built using only HTML and CSS",
"path": "/blogs/pure-css-todo-app",
"publishedAt": "2025-04-10T18:30:00.000Z",
"site": "at://did:plc:ygdeyuxj3bfk42zacptp3sw7/site.standard.publication/3mnwwvatoog2l",
"tags": [
"web"
],
"textContent": "Checkout - https://duskyelf.github.io/todo-css/ <br />\nSource - https://github.com/DuskyElf/todo-css\n\nThis is a fully functional Todo app that I recently built using only HTML and CSS, YES NO JavaScript!!\nThis blog post will walk you through the creative techniques I used to make this work, including state management with HTML form controls, CSS selectors, and custom properties.\n\nHow It Works?\n\nState Management: HTML Form Controls\n\nThe magic happens through cleverly placed input elements that manage our app's state:\n\nThese inputs are still functional but hidden from view with some CSS tricks:\n\nEach hidden input gets wrapped with a custom-styled label that acts as its visual stand-in. The cool thing about putting an input inside a label is that clicking anywhere on the label triggers the input. We can then target these labels with selectors like label:has(>input:checked) to apply different styles based on their state.\n\nHere's a quick example:\n\nThe key insight here is that these input elements are our only way to interact with the app. While we can't directly manipulate the DOM with CSS, we can change styles based on input states. This creates the illusion of interactivity by showing or hiding elements depending on the current state of our inputs.\n\n\"Database\" Structure\n\nOur app's \"database\" is really just a collection of pre-defined HTML elements:\n\nThis gives us a fixed number of todo slots (pre-allocated in the HTML). We show or hide these elements using conditional CSS that acts a bit like database queries against the current state.\n\nCSS Selector-Based Queries\n\nHere's where things get really interesting! We use some pretty advanced CSS selectors to mimic what would normally be JavaScript logic:\n\nThis is basically saying:\n\nAnd for the \"Done\" tab:\n\nWhich translates to:\n\nPretty clever, right?\n\nChain Reaction for Task Creation\n\nOne of my favorite tricks in this app is how we handle adding new tasks. Each todo item has an \"add\" button (actually a radio button) that controls whether the next todo item appears:\n\nTo make it look like we're dynamically adding tasks, we only show the first unchecked add button and hide all the rest:\n\nThemeable UI via CSS Variables\n\nWant to switch themes? No problem! The app uses a nifty theming system built with CSS variables:\n\nWhen you pick a theme, the conditional :has() selector figures out which radio button is checked and defines the theme variables on the root accordingly. Throughout the app, all elements depend on these variables while defining their styles.\n\nTechnical Limitations\n\nWhile this CSS-only approach is super cool, it does come with some important limitations to keep in mind:\n\nCSS is fundamentally designed for styling pages, not creating application logic. It cannot directly interact with or manipulate the DOM like JavaScript can. Instead, we're cleverly styling elements to be visible or hidden based on the state of input elements.\n\nThere's essentially no equivalent to JavaScript's dynamic memory allocation (like malloc() or free()) in CSS. This means all possible todo items must be predefined in the HTML, just waiting to be revealed when needed. It's like having a fixed-size array instead of a dynamic data structure!\n\nThis approach also makes it impossible to:\n\n- Save todos between sessions (no localStorage or database integration)\n- Dynamically generate new UI components beyond what's in the initial HTML\n- Implement complex operations like sorting, filtering beyond simple visibility toggles\n- Connect to external services or APIs\n\nDespite these constraints, it's fascinating to see just how far we can push CSS with creative thinking. This project demonstrates that sometimes limitations can spark the most innovative solutions!\n\nBrowser Compatibility\n\nFor this CSS wizardry to work, you'll need a browser that supports the :has() selector. Since December 2023, this feature works across the latest devices and browser versions. This feature might not work in older devices or browsers.\n\n- Chrome/Edge 105+\n- Firefox 121+\n- Safari 15.4+\n- Opera 91+\n\nTechnical References\n\nWant to dive deeper? Check out these resources:\n\n- CSS :has() Selector\n- CSS Custom Properties\n- CSS Combinators",
"title": "Zero JS, Pure CSS ToDo App"
}