{"id":1020,"date":"2024-02-28T15:17:43","date_gmt":"2024-02-28T21:17:43","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=1020"},"modified":"2024-02-28T17:37:43","modified_gmt":"2024-02-28T23:37:43","slug":"building-a-todo-app-from-scratch-step-4-styling-interactive-choices","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/building-a-todo-app-from-scratch-step-4-styling-interactive-choices\/","title":{"rendered":"Building a TODO App from Scratch \u2014 Step 4 \u2014 Styling &#038; Interactive Choices"},"content":{"rendered":"\n<p>I&#8217;m already a little over all the green. What was I <a href=\"https:\/\/frontendmasters.com\/blog\/building-a-todo-app-from-scratch-step-1-planning-design\/\">thinking<\/a>? I&#8217;m much more of a nice gray color palette kinda guy with some fun poppy colors. That&#8217;s what we need to get to here. When we left off, we just had wireframes going. It&#8217;s time to get some style on this thing!<\/p>\n\n\n<div class=\"box article-series\">\n  <header>\n    <h3 class=\"article-series-header\">Article Series<\/h3>\n  <\/header>\n  <div class=\"box-content\">\n            <ol>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/building-a-todo-app-from-scratch-step-1-planning-design\/\">Building a TODO App from Scratch \u2014 Step 1 \u2014 Planning &#038; Design<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/building-a-todo-app-from-scratch-step-2-html\/\">Building a TODO App from Scratch \u2014 Step 2 \u2014 HTML<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/building-a-todo-app-from-scratch-step-3-basic-javascript-functionality\/\">Building a TODO App from Scratch \u2014 Step 3 \u2014 Basic JavaScript Functionality<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/building-a-todo-app-from-scratch-step-4-styling-interactive-choices\/\">Building a TODO App from Scratch \u2014 Step 4 \u2014 Styling &#038; Interactive Choices<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/building-a-todo-app-from-scratch-step-5-extra-functionality\/\">Building a TODO App from Scratch \u2014 Step 5 \u2014 Extra Functionality<\/a>\n            <\/li>\n                  <\/ol>\n        <\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Custom Properties<\/h2>\n\n\n\n<p>We can get rid of that wireframe look by removing all those 1px borders and applying some backgrounds. Let&#8217;s give ourselves some Custom Properties to work with, so we can apply colors in such a way they are easy to change later. <\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">html<\/span> {\n  <span class=\"hljs-attribute\">--gray800<\/span>: <span class=\"hljs-built_in\">oklch<\/span>(<span class=\"hljs-number\">10%<\/span> <span class=\"hljs-number\">0%<\/span> <span class=\"hljs-number\">0<\/span>);\n  <span class=\"hljs-attribute\">--gray600<\/span>: <span class=\"hljs-built_in\">oklch<\/span>(<span class=\"hljs-number\">40%<\/span> <span class=\"hljs-number\">0%<\/span> <span class=\"hljs-number\">0<\/span>);\n  <span class=\"hljs-attribute\">--gray100<\/span>: <span class=\"hljs-built_in\">oklch<\/span>(<span class=\"hljs-number\">92%<\/span> <span class=\"hljs-number\">0%<\/span> <span class=\"hljs-number\">0<\/span>);\n  <span class=\"hljs-attribute\">--brand<\/span>: <span class=\"hljs-built_in\">oklch<\/span>(<span class=\"hljs-number\">85%<\/span> <span class=\"hljs-number\">0.3<\/span> <span class=\"hljs-number\">145<\/span>);\n\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">var<\/span>(--gray100);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You can see we&#8217;re using the lightest gray right away in colorizing the background. <\/p>\n\n\n\n<p>I&#8217;m using OKLCH color function here because <a href=\"https:\/\/chriscoyier.net\/2023\/01\/22\/ok-oklch-%F0%9F%91%91\/\">I find it the generally-nicest<\/a> of all the color models. One very immediate thing it does for us here is that it unlocks P3 colors. That <code>--brand<\/code> color you see there is actually a bit more bright and vibrant than is even possible in the sRGB color space (like a hex code or any of the more common color functions you may be familiar with, like <code>rgb()<\/code>). <\/p>\n\n\n\n<p>Applying those colors to the relevant sections gets us a start:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"476\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.03.17%E2%80%AFAM.png?resize=1024%2C476&#038;ssl=1\" alt=\"\" class=\"wp-image-1064\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.03.17%E2%80%AFAM.png?resize=1024%2C476&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.03.17%E2%80%AFAM.png?resize=300%2C140&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.03.17%E2%80%AFAM.png?resize=768%2C357&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.03.17%E2%80%AFAM.png?w=1496&amp;ssl=1 1496w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Fonts<\/h2>\n\n\n\n<p>We don&#8217;t need to go too far down the typography rabbit hole here, as all our design calls for is the header, one input, and a list of to-do items. Keeping it simple and fast (not loading fonts is&#8230; fast), is ideal, so:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">html<\/span> {\n  <span class=\"hljs-attribute\">font-family<\/span>: system-ui, sans-serif;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You&#8217;d think <code>system-ui<\/code> would be boring, but as a little fun and progressive enhancement, we can use some <code>font-variation-settings<\/code> on it in the case that the font that loads supports them. It&#8217;s not the only one, but <a href=\"https:\/\/chriscoyier.net\/2022\/08\/02\/actually-the-san-francisco-typeface-does-ship-as-a-variable-font\/\">Apple&#8217;s San Francisco font ships as a variable font<\/a>, so we can do fun things like stretch it out. <\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">header<\/span> {\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">var<\/span>(--gray800);\n  <span class=\"hljs-attribute\">color<\/span>: white;\n  h1 {\n    <span class=\"hljs-attribute\">font-variation-settings<\/span>: <span class=\"hljs-string\">\"wght\"<\/span> <span class=\"hljs-number\">900<\/span>, <span class=\"hljs-string\">\"wdth\"<\/span> <span class=\"hljs-number\">700<\/span>;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>I love this look, myself:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"446\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.04.11%E2%80%AFAM.png?resize=1024%2C446&#038;ssl=1\" alt=\"\" class=\"wp-image-1065\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.04.11%E2%80%AFAM.png?resize=1024%2C446&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.04.11%E2%80%AFAM.png?resize=300%2C131&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.04.11%E2%80%AFAM.png?resize=768%2C335&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.04.11%E2%80%AFAM.png?w=1500&amp;ssl=1 1500w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">SVGs<\/h2>\n\n\n\n<p>We need SVG&#8217;s for the &#8220;check&#8221; of the logo and that we&#8217;ll use when a user checks off a to-do. I&#8217;m kinda thinking we just re-use the same one for both as that just makes good sense. Plus we&#8217;ll make a &#8220;+&#8221; SVG too. It might be tempting to find unicode characters for these, but I&#8217;m telling you, just use SVG. The predictable bounding box of SVG will be your friend (no fighting against weird space).<\/p>\n\n\n\n<p>There are loads of resources for finding SVGs to use. I&#8217;m usually a <a href=\"https:\/\/thenounproject.com\/\">Noun Project<\/a> guy, but if we needed, say, 10 or more icons I&#8217;d probably try to find a more cohesive set somewhere. These are also so simple that could (should) draw them ourselves (even directly in code), but we&#8217;ll save that for another day. <\/p>\n\n\n\n<p>Ultimately, we&#8217;ll just get our hands on a &#8220;check&#8221; icon and use it in the logo&#8230;<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span> <span class=\"hljs-attr\">...<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"svg-check site-logo\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">polyline<\/span> <span class=\"hljs-attr\">...<\/span> \/&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>TODOs<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>&#8230; in our template literal for each to-do &#8230;<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">HTML += <span class=\"hljs-string\">`&lt;li id=\"<span class=\"hljs-subst\">${todo.id}<\/span>\"&gt;\n  <span class=\"hljs-subst\">${todo.title}<\/span>\n  &lt;button aria-label=\"Complete\" class=\"button-complete\"&gt;\n    &lt;svg ... class=\"svg-check\"&gt;\n      &lt;polyline ... \/&gt;\n    &lt;\/svg&gt;\n  &lt;\/button&gt;\n&lt;\/li&gt;`<\/span>;<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>&#8230; and then our &#8220;+&#8221; in the form:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">&lt;form id=<span class=\"hljs-string\">\"todo-form\"<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span><\/span>=<span class=\"hljs-string\">\"todo-form\"<\/span>&gt;\n  <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"screen-reader-text\"<\/span>&gt;<\/span>New TODO<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\"<\/span> <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"todo\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"button-add-todo\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"button-add-todo\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span> <span class=\"hljs-attr\">...<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"svg-plus\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">g<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">line<\/span> <span class=\"hljs-attr\">...<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">line<\/span> <span class=\"hljs-attr\">...<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">g<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>There is a lot to know about SVG so I&#8217;m really just dropping the basic usage in here. I&#8217;ve chosen to drop the SVG code right &#8220;inline&#8221; with the HTML because I generally think that&#8217;s best because it&#8217;s fast and offers the most styling control. If you wanna dig more into SVG, I did <a href=\"https:\/\/abookapart.com\/products\/practical-svg\">write a book about it<\/a> one time you could check out. <\/p>\n\n\n\n<p>With the SVG&#8217;s I chose in there:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"470\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.06.36%E2%80%AFAM.png?resize=1024%2C470&#038;ssl=1\" alt=\"\" class=\"wp-image-1066\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.06.36%E2%80%AFAM.png?resize=1024%2C470&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.06.36%E2%80%AFAM.png?resize=300%2C138&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.06.36%E2%80%AFAM.png?resize=768%2C352&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/Screenshot-2024-02-28-at-11.06.36%E2%80%AFAM.png?w=1500&amp;ssl=1 1500w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Adding To-Do Interactions<\/h2>\n\n\n\n<p>When we add a new to-do, I want to give the adding button a temporary class so we can do something fun with it. So we&#8217;ll use a saved reference to that button and do that:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">buttonAddTodo.classList.add(<span class=\"hljs-string\">\"added\"<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>But this is an opportunity! We could add a <em>different<\/em> class upon a failure. What failure? <em>Not entering any text<\/em>, for one. We could make the call not to allow entering blank to-dos, which at this point I think is a decent idea. <\/p>\n\n\n\n<p>So the form event handler then becomes like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">form.addEventListener(<span class=\"hljs-string\">\"submit\"<\/span>, (event) =&gt; {\n  event.preventDefault();\n  <span class=\"hljs-comment\">\/\/ Don't allow empty todo<\/span>\n  <span class=\"hljs-keyword\">if<\/span> (!form&#91;<span class=\"hljs-number\">0<\/span>].value) {\n    buttonAddTodo.classList.add(<span class=\"hljs-string\">\"shake\"<\/span>);\n    <span class=\"hljs-keyword\">return<\/span>;\n  }\n  addTodo(event);\n  form.reset();\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Now check out these little @keyframe animations I can do with those classes:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.button-add-todo<\/span> {\n  ...\n\n  &amp;.shake {\n    <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">0deg<\/span>;\n    <span class=\"hljs-attribute\">transform-origin<\/span>: bottom right;\n    <span class=\"hljs-attribute\">animation<\/span>: shake <span class=\"hljs-number\">0.4s<\/span> <span class=\"hljs-built_in\">cubic-bezier<\/span>(<span class=\"hljs-number\">0.36<\/span>, <span class=\"hljs-number\">0.07<\/span>, <span class=\"hljs-number\">0.19<\/span>, <span class=\"hljs-number\">0.97<\/span>) both;\n  }\n  &amp;<span class=\"hljs-selector-class\">.added<\/span> {\n    <span class=\"hljs-attribute\">transform-origin<\/span>: center center;\n    <span class=\"hljs-attribute\">animation<\/span>: added <span class=\"hljs-number\">0.4s<\/span> <span class=\"hljs-built_in\">cubic-bezier<\/span>(<span class=\"hljs-number\">0.36<\/span>, <span class=\"hljs-number\">0.07<\/span>, <span class=\"hljs-number\">0.19<\/span>, <span class=\"hljs-number\">0.97<\/span>) both;\n  }\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> shake {\n  50% {\n    <span class=\"hljs-attribute\">rotate<\/span>: -<span class=\"hljs-number\">12deg<\/span>;\n  }\n}\n<span class=\"hljs-keyword\">@keyframes<\/span> added {\n  50% {\n    <span class=\"hljs-attribute\">rotate<\/span>: <span class=\"hljs-number\">1turn<\/span>;\n    <span class=\"hljs-attribute\">translate<\/span>: <span class=\"hljs-number\">0<\/span> <span class=\"hljs-number\">50px<\/span>;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"566\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/CleanShot-2024-02-28-at-11.44.14.gif?resize=800%2C566&#038;ssl=1\" alt=\"\" class=\"wp-image-1070\" style=\"width:593px;height:auto\"\/><\/figure>\n\n\n\n<p>A little fun, maybe?<\/p>\n\n\n\n<p>Let&#8217;s be careful to <em>remove<\/em> those classes after the animation happens, so that another animation can be triggered freshly when needed:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">buttonAddTodo.addEventListener(<span class=\"hljs-string\">\"animationend\"<\/span>, () =&gt; {\n  buttonAddTodo.classList.remove(<span class=\"hljs-string\">\"shake\"<\/span>, <span class=\"hljs-string\">\"added\"<\/span>);\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Completing To-Do Interactions<\/h2>\n\n\n\n<p>We&#8217;ve got a big ol&#8217; box sitting there waiting for to-do items to be checked. And now that we&#8217;re using SVG, my temptation is to do a little fun &#8220;line drawing&#8221; with that. That is, see the checkbox be &#8220;drawn&#8221; as we complete the to-do, before removing it. <\/p>\n\n\n\n<p>Here&#8217;s the trick. <\/p>\n\n\n\n<p>First we go find the SVG itself in our template literal, and add this special <code>pathLength<\/code> attribute to the actual shape:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-code-table\"><span class='shcb-loc'><span><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">\"20\"<\/span> <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">\"20\"<\/span> <span class=\"hljs-attr\">viewBox<\/span>=<span class=\"hljs-string\">\"0 0 241.44 259.83\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"svg-check\"<\/span>&gt;<\/span>\n<\/span><\/span><mark class='shcb-loc'><span>  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">polyline<\/span> <span class=\"hljs-attr\">pathLength<\/span>=<span class=\"hljs-string\">\"1\"<\/span> <span class=\"hljs-attr\">points<\/span>=<span class=\"hljs-string\">\"16.17 148.63 72.17 225.63 225.17 11.63\"<\/span> \/&gt;<\/span>\n<\/span><\/mark><span class='shcb-loc'><span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>This just makes animating it much easier.<\/p>\n\n\n\n<p>Then we set and animate some specific SVG properties to <a href=\"https:\/\/css-tricks.com\/a-trick-that-makes-drawing-svg-lines-way-easier\/\">do the drawing<\/a>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.todo-list<\/span> {\n  ...\n  li {\n    ...\n    .svg-check {\n      <span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">0<\/span>;\n    }\n    &amp;<span class=\"hljs-selector-class\">.complete<\/span> {\n      .svg-check {\n        <span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">1<\/span>;\n        <span class=\"hljs-attribute\">stroke-dasharray<\/span>: <span class=\"hljs-number\">1<\/span>;\n        <span class=\"hljs-attribute\">stroke-dashoffset<\/span>: <span class=\"hljs-number\">1<\/span>;\n        <span class=\"hljs-attribute\">animation<\/span>: do-check <span class=\"hljs-number\">1s<\/span> infinite alternate;\n      }\n    }\n  }\n}\n\n<span class=\"hljs-keyword\">@keyframes<\/span> do-check {\n  <span class=\"hljs-selector-tag\">from<\/span> {\n    <span class=\"hljs-attribute\">stroke-dashoffset<\/span>: <span class=\"hljs-number\">1<\/span>;\n  }\n  <span class=\"hljs-selector-tag\">to<\/span> {\n    <span class=\"hljs-attribute\">stroke-dashoffset<\/span>: <span class=\"hljs-number\">0<\/span>;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Check out the drawing effect at work:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"470\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/CleanShot-2024-02-28-at-12.33.33.gif?resize=800%2C470&#038;ssl=1\" alt=\"\" class=\"wp-image-1071\" style=\"width:416px;height:auto\"\/><\/figure>\n\n\n\n<p>The trick to getting the animation to run before being removed is just to &#8230; wait.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">removeTodo<\/span>(<span class=\"hljs-params\">event<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> listItem = event.target.parentElement;\n  listItem.classList.toggle(<span class=\"hljs-string\">\"complete\"<\/span>);\n  setTimeout(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    TODOs = TODOs.filter(<span class=\"hljs-function\">(<span class=\"hljs-params\">todo<\/span>) =&gt;<\/span> todo.id !== event.target.parentElement.id);\n    localStorage&#91;<span class=\"hljs-string\">\"data\"<\/span>] = <span class=\"hljs-built_in\">JSON<\/span>.stringify(TODOs);\n    buildUI();\n  }, <span class=\"hljs-number\">1000<\/span>);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You can see I&#8217;m just waiting for a second there, which is about the same, a little longer, than the animation itself. We could tie it to the <code>animationend<\/code> event here also, but for some reason it felt better to me to leave this bit of business logic in the code here rather than tying it to a CSS thing. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Really smoooooth completions<\/h3>\n\n\n\n<p>What if we remove a to-do from the <em>middle<\/em> of the list. It&#8217;ll be <em>jerky<\/em> won&#8217;t it? It certainly could be. This is one of those classic situations in CSS where being able to &#8220;animate to\/from auto&#8221; would be nice. In this case, we want to animate from &#8220;however tall a list item is&#8221; to zero. That way the bottom items in the list would slide up, making a much more comfortable animation. But honestly, even animating the height here feels like slight of hand, what I actually want is just not to think very hard about it and for the list items to just slide up into place, if at all possible.<\/p>\n\n\n\n<p>Turns out this is a lovely use-case for <a href=\"https:\/\/developer.chrome.com\/docs\/web-platform\/view-transitions\">View Transitions<\/a>. Without getting very deep at all into it, essentially all we need to do is:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Give all our to-dos a unique <code>view-transition-name<\/code><\/li>\n\n\n\n<li>When we update the DOM, e.g. <code>buildUI()<\/code>, wrap that in a <code>startViewTransition()<\/code> function.<\/li>\n<\/ol>\n\n\n\n<p>So our template literal is updated to be like&#8230;<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">  TODOs.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">todo<\/span>) =&gt;<\/span> {\n    HTML += <span class=\"hljs-string\">`&lt;li id=\"<span class=\"hljs-subst\">${todo.id}<\/span>\" style=\"view-transition-name: list-item-<span class=\"hljs-subst\">${todo.id}<\/span>;\"&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>We already have a unique ID so we&#8217;ll steal that for the dual purpose here. <\/p>\n\n\n\n<p>Then wrap our DOM updating function like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">if<\/span> (!<span class=\"hljs-built_in\">document<\/span>.startViewTransition) {\n  buildUI();\n} <span class=\"hljs-keyword\">else<\/span> {\n  <span class=\"hljs-built_in\">document<\/span>.startViewTransition(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    buildUI();\n  });\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That&#8217;s the same and progressive-enhancement friendly way to do it. It&#8217;s kind of required here, as that <code>startViewTransition<\/code> will throw in browsers that don&#8217;t support it and it could break our whole site.<\/p>\n\n\n\n<p>Look at us know:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"800\" height=\"778\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/CleanShot-2024-02-28-at-12.48.25.gif?resize=800%2C778&#038;ssl=1\" alt=\"\" class=\"wp-image-1072\" style=\"width:493px;height:auto\"\/><\/figure>\n\n\n\n<p>This is friggin&#8217; cool if you ask me. <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">So far<\/h2>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_YzgboPm\" src=\"\/\/codepen.io\/anon\/embed\/YzgboPm?height=450&amp;theme-id=47434&amp;slug-hash=YzgboPm&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed YzgboPm\" title=\"CodePen Embed YzgboPm\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Just a little bit more functionality to go!<\/p>\n\n\n<div class=\"box article-series\">\n  <header>\n    <h3 class=\"article-series-header\">Article Series<\/h3>\n  <\/header>\n  <div class=\"box-content\">\n            <ol>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/building-a-todo-app-from-scratch-step-1-planning-design\/\">Building a TODO App from Scratch \u2014 Step 1 \u2014 Planning &#038; Design<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/building-a-todo-app-from-scratch-step-2-html\/\">Building a TODO App from Scratch \u2014 Step 2 \u2014 HTML<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/building-a-todo-app-from-scratch-step-3-basic-javascript-functionality\/\">Building a TODO App from Scratch \u2014 Step 3 \u2014 Basic JavaScript Functionality<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/building-a-todo-app-from-scratch-step-4-styling-interactive-choices\/\">Building a TODO App from Scratch \u2014 Step 4 \u2014 Styling &#038; Interactive Choices<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/frontendmasters.com\/blog\/building-a-todo-app-from-scratch-step-5-extra-functionality\/\">Building a TODO App from Scratch \u2014 Step 5 \u2014 Extra Functionality<\/a>\n            <\/li>\n                  <\/ol>\n        <\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;m already a little over all the green. What was I thinking? I&#8217;m much more of a nice gray color palette kinda guy with some fun poppy colors. That&#8217;s what we need to get to here. When we left off, we just had wireframes going. It&#8217;s time to get some style on this thing! Custom [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1076,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[7,3,91,6,101],"class_list":["post-1020","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-css","tag-javascript","tag-svg","tag-todo","tag-view-transitions"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2024\/02\/todo-thumb-2.jpg?fit=1000%2C500&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/1020","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=1020"}],"version-history":[{"count":12,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/1020\/revisions"}],"predecessor-version":[{"id":1079,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/1020\/revisions\/1079"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/1076"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=1020"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=1020"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=1020"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}