├── LICENSE
├── README.md
└── index.html
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Rehmatpal Singh
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pure CSS Todo App
2 |
3 | This is a fully functional Todo app built with just HTML and CSS — JavaScript OUT THE WINDOW! While I wouldn't recommend this approach for your next production app (more on the limitations below), it's a fun way to explore how creative we can get with CSS and HTML elements. Plus, it shows off a pretty cool implementation of theme selection without needing any scripts!
4 |
5 | It's live on - https://duskyelf.github.io/todo-css/
6 |
7 | ## How It Works?
8 |
9 | ### State Management: HTML Form Controls
10 |
11 | The magic happens through cleverly placed input elements that manage our app's state:
12 |
13 | ```html
14 |
15 |
16 |
17 | ```
18 |
19 | These inputs are still functional but hidden from view with some CSS tricks:
20 |
21 | ```css
22 | input[type="radio"],
23 | input[type="checkbox"] {
24 | width: 0;
25 | opacity: 0;
26 | cursor: pointer;
27 | position: absolute;
28 | }
29 | ```
30 |
31 | Each 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.
32 |
33 | Here's a quick example:
34 |
35 | ```html
36 |
37 | ```
38 |
39 | ```css
40 | [done-btn]:has(>input:checked) {
41 | color: var(--secondary);
42 | }
43 | ```
44 |
45 | The 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.
46 |
47 | ### "Database" Structure
48 |
49 | Our app's "database" is really just a collection of pre-defined HTML elements:
50 |
51 | ```html
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | ```
61 |
62 | This 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.
63 |
64 | ### CSS Selector-Based Queries
65 |
66 | Here's where things get really interesting! We use some pretty advanced CSS selectors to mimic what would normally be JavaScript logic:
67 |
68 | ```css
69 | body:has(label[nav-todo]>input:checked) .todo-data:has(label[done-btn]>input:checked) {
70 | display: none;
71 | }
72 | ```
73 |
74 | This is basically saying:
75 | ```
76 | IF we're on the "todo" tab AND the task is marked as complete THEN
77 | hide that task
78 | END IF
79 | ```
80 |
81 | And for the "Done" tab:
82 |
83 | ```css
84 | body:has(label[nav-done]>input:checked) .todo-data:not(:has(label[done-btn]>input:checked)) {
85 | display: none;
86 | }
87 | ```
88 |
89 | Which translates to:
90 | ```
91 | IF we're on the "done" tab AND the task is NOT complete THEN
92 | hide that task
93 | END IF
94 | ```
95 |
96 | Pretty clever, right?
97 |
98 | ### Chain Reaction for Task Creation
99 |
100 | One 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:
101 |
102 | ```css
103 | /* Hide the todo item if its preceding add button is not checked */
104 | [add-btn]:has(>input:not(:checked))+.todo-data {
105 | display: none;
106 | }
107 |
108 | /* Hide the add button once it's been clicked */
109 | [add-btn]:has(>input:checked) {
110 | display: none;
111 | }
112 | ```
113 |
114 | To make it look like we're dynamically adding tasks, we only show the first unchecked add button and hide all the rest:
115 |
116 | ```css
117 | /* Hide all subsequent add buttons */
118 | [add-btn]:not(:has(>input:checked))~[add-btn] {
119 | display: none;
120 | }
121 | ```
122 |
123 | ### Themeable UI via CSS Variables
124 |
125 | Want to switch themes? No problem! The app uses a nifty theming system built with CSS variables:
126 |
127 | ```css
128 | :root:has([theme-s] label:nth-child(1)>input:checked) {
129 | --text: #e7e4e9;
130 | --background: #110c13;
131 | --primary: #c1a4d2;
132 | --secondary: #5f2e7a;
133 | --accent: #9e4acd;
134 | }
135 |
136 | :root:has([theme-s] label:nth-child(2)>input:checked) {
137 | --text: #1a171c;
138 | --background: #f1ecf3;
139 | --primary: #4b2d5c;
140 | --secondary: #b685d1;
141 | --accent: #8431b4;
142 | }
143 |
144 | /* ... more themes */
145 |
146 | ```
147 |
148 | When 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.
149 |
150 | ## Technical Limitations
151 |
152 | While this CSS-only approach is super cool, it does come with some important limitations to keep in mind:
153 |
154 | CSS 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.
155 |
156 | There'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!
157 |
158 | This approach also makes it impossible to:
159 | - Save todos between sessions (no localStorage or database integration)
160 | - Dynamically generate new UI components beyond what's in the initial HTML
161 | - Implement complex operations like sorting, filtering beyond simple visibility toggles
162 | - Connect to external services or APIs
163 |
164 | Despite 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!
165 |
166 | ## Browser Compatibility
167 |
168 | For 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.
169 | - Chrome/Edge 105+
170 | - Firefox 121+
171 | - Safari 15.4+
172 | - Opera 91+
173 |
174 | ## Technical References
175 |
176 | Want to dive deeper? Check out these resources:
177 | - [CSS :has() Selector](https://developer.mozilla.org/en-US/docs/Web/CSS/:has)
178 | - [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties)
179 | - [CSS Combinators](https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors/Combinators)
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |