# TheoSoti — Full Content Theosoti is a frontend blog sharing CSS tutorials, practical guides, and bite-sized tips on modern web development and UI design. Author: Theo Soti — Frontend Developer & Content Creator. Site: https://theosoti.com ================================================================ BLOG POSTS ================================================================ # CSS if(): Conditional Styling Without JavaScript URL: https://theosoti.com/blog/css-if-function-conditional-styling-without-javascript/ Published: 2026-06-23 Tags: CSS, modern CSS, JavaScript alternatives, frontend development, frontend tutorial > Use the CSS if() function to choose property values from style, media, and support queries without writing JavaScript state glue. ## CSS can now choose a value Most CSS conditions work at the rule level. If the viewport is narrow, run this block: ```css @media (width < 700px) { .card { padding: 1rem; } } ``` If the browser supports a feature, run this block: ```css @supports (color: oklch(60% 0.2 30)) { .card { color: oklch(60% 0.2 30); } } ``` That model works well when a whole set of declarations needs to change. It feels heavier when only one value is conditional. The new CSS `if()` function works inside a property value: ```css .card { padding: if( media(width < 700px): 1rem; else: 1.5rem; ); } ``` That is the mental model. `if()` does not choose a selector. It does not create a new block in the cascade. It chooses a value for the property you are already writing. That makes it useful for small branches: one color, one padding value, one part of a border shorthand, one token inside a component. It is also early. MDN marks `if()` as experimental and not Baseline. Can I Use shows support in Chrome and Edge from version 137, but not Safari or Firefox on June 23, 2026. Treat it as progressive enhancement unless your browser target is Chromium-only. ## The mental model An `if()` value is a list of branches. Each branch has a condition, a colon, and the value to use when that condition is true: ```css .alert { background: if( style(--tone: danger): oklch(96% 0.04 28); else: white; ); } ``` The browser reads the branches from top to bottom. The first true condition wins. If nothing matches, the `else` branch gives you a default. Write the `else` branch. Without it, a supported browser can end up with an invalid or initial value when no condition matches. That is rarely what you meant. There is also a tiny syntax detail that matters: there must be no space between `if` and `(`. ```css /* valid */ color: if(media(print): black; else: white); /* invalid */ color: if (media(print): black; else: white); ``` That is annoying, but easy to catch once you know it. ## Start with style queries The most useful form for components is probably `style()`. It lets one declaration read a custom property on the same element and choose a value from it. ```css .button { --tone: neutral; background: if( style(--tone: danger): oklch(60% 0.22 28); style(--tone: success): oklch(62% 0.18 150); else: oklch(92% 0 0); ); color: if( style(--tone: neutral): black; else: white; ); } ``` Then your component can set one custom property: ```css .button[data-tone='danger'] { --tone: danger; } ``` The interesting part is that you do not need to repeat the full button rule for every tone. You can keep the decision next to the value that changes. That is where `if()` feels different from a class-based pattern. Classes still decide what state the component is in. `if()` decides what each value should become for that state. The demo uses native radio inputs, `:has()`, custom properties, and `if()`. There is no JavaScript fallback. ## Use media queries when one value changes `if()` can also run a media query inside a value. ```css .layout { gap: if( media(width < 700px): 0.75rem; else: 1.5rem; ); } ``` This is useful when a single value changes and the rule would otherwise exist only for that one line. I would not replace every `@media` block with this. If the layout changes several declarations, a normal media query is clearer: ```css @media (width < 700px) { .layout { grid-template-columns: 1fr; gap: 0.75rem; padding: 1rem; } } ``` That code is easier to scan because the whole layout mode is in one place. Use `if(media(...))` when the branch is small. Use `@media` when the branch is the layout. ## Use supports queries for value fallbacks Feature queries work too. ```css .badge { color: if( supports(color: oklch(70% 0.2 30)): oklch(70% 0.2 30); else: hotpink; ); } ``` That can be handy when a modern value has a fallback and you want to keep the fallback inside the same declaration. For unsupported browsers, you still need the normal cascade fallback because old browsers do not understand `if()` at all: ```css .badge { color: hotpink; color: if( supports(color: oklch(70% 0.2 30)): oklch(70% 0.2 30); else: hotpink; ); } ``` The first declaration is for browsers without `if()`. The second declaration is for browsers with `if()`. That pattern is boring, but it is the right kind of boring. ## You can use it inside shorthands `if()` can return a whole property value: ```css .callout { border: if( style(--tone: danger): 2px solid oklch(60% 0.22 28); else: 2px solid var(--border); ); } ``` It can also return only part of a value: ```css .callout { border: 2px solid if( style(--tone: danger): oklch(60% 0.22 28); else: var(--border); ); } ``` That second version is often nicer. The border width and style stay fixed. Only the color branches. The same idea works inside other values: ```css .panel { width: calc( if(style(--wide: true): 70%; else: 50%) - 2rem ); } ``` Do not get carried away. Nested conditional values can become harder to read than repeated CSS. If you need to stop and mentally parse three branches inside a `calc()`, a separate rule might be better. ## Browser support This is the part that should stop you from using `if()` casually. On June 23, 2026, [Can I Use reports 65.31% global support for CSS `if()`](https://caniuse.com/css-if). MDN also marks `if()` as limited availability and experimental. That does not make it useless, but it does mean you should treat it as progressive enhancement. So the safe pattern is: ```css .card { padding: 1rem; } @supports (padding: if(media(width > 0px): 1rem; else: 2rem)) { .card { padding: if( style(--density: compact): 0.75rem; else: 1.25rem; ); } } ``` The fallback comes first. The `if()` version lives inside `@supports`. For a tutorial, that is also a good teaching pattern. The browser either shows the live version or it shows a plain message explaining why the demo is unavailable. No fake polyfill. No JavaScript fallback hiding the real support story. ## Final thoughts CSS `if()` is not ready to be a default tool for every production site. Safari and Firefox support are still missing, and the syntax is new enough that a lot of developers will need to look twice. But the feature itself is useful. It fills a small gap in CSS: sometimes you do not want a conditional block. You want one conditional value. That is what `if()` gives you. Use it behind `@supports`, keep the fallback boring, and reserve it for the places where it makes the CSS easier to read. ## Enjoyed this article? I write about modern CSS, HTML, and simpler ways to build for the web. Join my newsletter below if you want more tutorials like this. ## References - [MDN: `if()` CSS function](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/if) - [CSS Values and Units Module Level 5: `if()` notation](https://drafts.csswg.org/css-values-5/#if-notation) - [Chrome 137 release notes](https://developer.chrome.com/blog/new-in-chrome-137/) - [Can I Use: CSS `if()` function](https://caniuse.com/css-if) --- # CSS Anchor Positioning: Tooltips and Dropdowns Without JavaScript Math URL: https://theosoti.com/blog/css-anchor-positioning-without-javascript/ Published: 2026-06-16 Tags: CSS, modern CSS, JavaScript alternatives, frontend development, CSS layout, accessibility > A modern CSS guide to Anchor Positioning for popovers, tooltips, and dropdowns without getBoundingClientRect, scroll listeners, or layout JavaScript. ## Why this matters Positioning a dropdown should not require a small geometry engine. You have a button. You have a menu. The menu should open next to the button, keep that relationship when the page scrolls, and avoid falling outside the viewport when there is not enough space. For years, that usually meant JavaScript: ```js const button = document.querySelector('.menu-button'); const menu = document.querySelector('.menu'); const rect = button.getBoundingClientRect(); menu.style.top = `${rect.bottom + 8}px`; menu.style.left = `${rect.left}px`; ``` That first version is simple, then real UI gets involved. The page scrolls. The viewport resizes. The button moves because a parent layout changed. The menu needs to flip above the button near the bottom of the screen. None of that is hard in isolation, but it is a lot of boring code for "put this thing next to that thing". CSS Anchor Positioning gives CSS the relationship it was missing. One element can become an anchor, and another element can position itself from that anchor. The browser already knows both boxes, so it can do the geometry without a `getBoundingClientRect()` loop. If you want to learn modern CSS through actual UI problems, this is a good feature to study. It moves a common floating UI pattern back into CSS, where the browser already understands layout, scrolling, and edges. ## The mental model Anchor positioning has two parts: you name the anchor, then you position another element from it. ```css .menu-button { anchor-name: --menu-button; } .menu { position: absolute; position-anchor: --menu-button; position-area: bottom; } ``` The button still lives in the normal layout. The menu is positioned, but it now has a reference point that is not just the viewport or its containing block. It can say "place me around that button". `position-area` is the friendliest syntax when you want a common placement like `top`, `bottom`, `left`, `right`, or `center`. It reads close to the design decision you would make in a component spec: ```css .menu { position-area: bottom; } ``` That is useful, but it is not the whole feature. Anchor positioning is not only `position-area`. ## You can position with anchor() The `anchor()` function lets you use the edges of the anchor inside inset properties like `top`, `right`, `bottom`, `left`, and their logical equivalents. This gives you more control than `position-area`. For example, this places the top edge of the menu 12px below the bottom edge of the button: ```css .menu-button { anchor-name: --menu-button; } .menu { position: absolute; position-anchor: --menu-button; inset: auto; top: calc(anchor(bottom) + 12px); left: anchor(left); } ``` You can also align the right edge of a dropdown with the right edge of the button: ```css .menu { position: absolute; position-anchor: --menu-button; inset: auto; top: calc(anchor(bottom) + 12px); right: anchor(right); } ``` Or you can place something above the trigger: ```css .tooltip { position: absolute; position-anchor: --help-button; inset: auto; bottom: calc(anchor(top) + 20px); left: anchor(center); translate: -50% 0; } ``` That last example is the point worth remembering. You are not locked into predefined areas. `anchor()` returns a length, so you can put it inside `calc()` and add spacing, offsets, or alignment tweaks. MDN's `anchor()` reference shows the same idea with examples like `top: calc(anchor(bottom) + 10px)` and `left: calc(anchor(right) + 10px)`. The important caveat is that `anchor()` works inside inset properties. You use it for positioning edges, not as a general value you can drop anywhere in CSS. ## `position-area` vs `anchor()` I would start with `position-area` when the design is simple. A popover under a button, a callout above an icon, or a small note to the side does not need custom math. ```css .popover { position: fixed; position-anchor: --trigger; position-area: bottom; margin: 0.5rem; } ``` Reach for `anchor()` when you need a specific edge relationship: ```css .popover { position: fixed; position-anchor: --trigger; inset: auto; top: calc(anchor(bottom) + 8px); right: anchor(right); } ``` Those two approaches can live in the same mental model: - `position-area` chooses a general zone around the anchor. - `anchor()` lets you wire individual inset properties to anchor edges. That distinction makes the feature much easier to use. You do not have to force everything through one syntax. ## Use it with popovers Anchor positioning is especially nice with the Popover API. Popover handles the opening behavior and the top layer. Anchor positioning handles placement. ```html ``` ```css .profile-button { anchor-name: --profile-button; } .profile-menu { position: fixed; position-anchor: --profile-button; inset: auto; top: calc(anchor(bottom) + 8px); left: anchor(left); } ``` The HTML opens the popover. The CSS places it. That is a clean split. When you position popovers this way, reset the browser's default popover positioning first. Popovers come with default `inset` and margin styles, and those can fight your anchor rules. ```css .profile-menu { margin: 0; inset: auto; } ``` If you want more detail on the opening part, I wrote about [opening dialogs and popovers with `commandfor`](/blog/html-command-commandfor-without-javascript/). This article is about the placement part. Once both pieces work, [`@starting-style` can animate the popover as it enters the top layer](/blog/starting-style-css/) without changing either one. ## Let the browser try another side The hard part of dropdown positioning is not the happy path. It is what happens near the edge of the viewport. This is where `position-try-fallbacks` helps. You can tell the browser to try a preferred placement first, then flip if that placement does not fit. ```css .profile-menu { position: fixed; position-anchor: --profile-button; position-area: bottom; position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline; } ``` That version says: place the menu below the button, but try another side if there is not enough room. This is not as powerful as a full tooltip library with custom collision rules, but it covers a lot of common UI. You can also use fallbacks with a more manual `anchor()` setup. Start with a usable default, then enhance where anchor positioning is supported: ```css .actions-menu { inset: 50% auto auto 50%; transform: translate(-50%, -50%); } @supports (anchor-name: --actions-menu-trigger) { .actions-menu { position: fixed; position-anchor: --actions-menu-trigger; inset: auto; transform: none; top: calc(anchor(bottom) + 8px); left: anchor(left); position-try-fallbacks: flip-block; } } ``` The fallback is not beautiful, but it is usable. In older browsers, the popover appears in the middle of the viewport. In browsers with anchor positioning, it attaches to the trigger. ## A copyable dropdown pattern For a small dropdown, I would start from this: ```html
Edit Duplicate
``` ```css .menu-trigger { anchor-name: --actions-menu-trigger; } .actions-menu { margin: 0; inset: 50% auto auto 50%; transform: translate(-50%, -50%); } @supports (anchor-name: --actions-menu-trigger) { .actions-menu { position: fixed; position-anchor: --actions-menu-trigger; inset: auto; transform: none; top: calc(anchor(bottom) + 0.5rem); right: anchor(right); position-try-fallbacks: flip-block, flip-inline; } } ``` This keeps the fallback simple and makes the enhanced version precise. The menu opens below the button, lines up with the button's right edge, and can flip when space gets tight. The simpler `position-area` version is still fine when exact edge alignment does not matter: ```css @supports (anchor-name: --actions-menu-trigger) { .actions-menu { position: fixed; position-anchor: --actions-menu-trigger; position-area: bottom; margin: 0.5rem; position-try-fallbacks: flip-block, flip-inline; } } ``` I like showing both versions because they solve slightly different problems. `position-area` is quick. `anchor()` is exact. ## Browser support and caveats Support is much better than it was in early anchor positioning demos. MDN now marks `anchor()` as Baseline 2026, and current support is broad enough to consider it for progressive enhancement. You should still use `@supports`, because not every user is on the latest browser and not every part of the spec lands at the same time. There are a few details worth testing before shipping: - `anchor()` is only valid in inset properties like `top`, `left`, `inset-block-start`, and `inset-inline-end`. - The anchor side has to match the axis. For example, `top: anchor(bottom)` makes sense, but `top: anchor(left)` does not. - Popovers need their default `inset` and margin reset if you want your anchor placement to win. - `position-try-fallbacks` handles common flipping, but it is not a complete replacement for every custom collision system. For current syntax and compatibility, use [MDN's anchor positioning guide](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Anchor_positioning), the [`anchor()` reference](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/anchor), [Can I Use](https://caniuse.com/css-anchor-positioning), and the [CSS Anchor Positioning specification](https://drafts.csswg.org/css-anchor-position-1/). ## When not to use it Anchor positioning places things. It does not design the whole interaction. You still need JavaScript if the menu content depends on app state, if opening the menu fetches data, if you are building a complex keyboard model, or if you need custom collision rules that the browser cannot express yet. You also still need to think about accessibility. A tooltip that only appears on hover is still a weak way to expose important content, even if the placement is now pure CSS. Use anchor positioning for the layout work. Use HTML for the native behavior where it fits. Keep JavaScript for the parts that are actually application logic. ## The short version CSS Anchor Positioning gives CSS a real relationship between a trigger and a floating element. Use `position-area` when you want a quick placement around the anchor: ```css .popover { position-anchor: --button; position-area: bottom; } ``` Use `anchor()` when you need exact edges: ```css .popover { position-anchor: --button; top: calc(anchor(bottom) + 8px); right: anchor(right); } ``` Add `position-try-fallbacks` when the element might run out of space: ```css .popover { position-try-fallbacks: flip-block, flip-inline; } ``` That combination replaces a surprising amount of small layout JavaScript. Not all of it, and not every tooltip library, but definitely the boring part where you measure a button just to put a menu next to it. --- # Open Dialogs and Popovers Without JavaScript Using commandfor URL: https://theosoti.com/blog/html-command-commandfor-without-javascript/ Published: 2026-06-08 Tags: HTML, modern HTML, JavaScript alternatives, accessibility, frontend development, frontend tutorial > Use HTML command and commandfor to control dialogs and popovers declaratively, with less JavaScript and native browser behavior. ## The tiny JavaScript we keep writing A lot of frontend JavaScript is not really application logic. Sometimes it is just glue. You have a button. You have a dialog. When the button is clicked, the dialog should open. So you write this: ```js const button = document.querySelector('.open-dialog'); const dialog = document.querySelector('#confirm-dialog'); button.addEventListener('click', () => { dialog.showModal(); }); ``` There is nothing wrong with this code. It is clear, small, and easy to understand. But it also feels strange. The browser already knows what a button is. The browser already knows what a dialog is. The browser already knows how to open a modal dialog. The only missing piece is the connection between the two. That is the gap `command` and `commandfor` fill. They let you describe the relationship directly in HTML: ```html

Delete this project?

This action cannot be undone.

``` No click listener. No `querySelector`. No tiny state wrapper just to call a native method. The button says what it controls, and what action it wants to perform. ## The mental model: a button can invoke a native action `commandfor` is the connection. It points to the `id` of the element you want to control: ```html ``` `command` is the action: ```html ``` Put them together and the button becomes an invoker. It can ask a supported element to do something the browser already understands. For dialogs, that can be: ```html ``` For popovers, that can be: ```html ``` This is not a replacement for all JavaScript. It is a replacement for the boring JavaScript that only exists to connect a button to a native browser behavior. That distinction matters. If your modal needs to fetch data, update app state, track events, or coordinate a complex workflow, JavaScript still belongs there. But if your code only says "open this dialog" or "toggle this popover", HTML can now carry that intent by itself. ## Open a dialog without JavaScript Native `` already gives you a lot: - modal behavior with `showModal()` - focus handling - Escape key behavior - a `::backdrop` - form-friendly close actions Before `commandfor`, opening the dialog still usually needed a small script: ```js openButton.addEventListener('click', () => { dialog.showModal(); }); ``` Now the open button can call the native action by itself: ```html

Newsletter settings

Choose how often you want to hear from us.

``` The important part is this: ```html command="show-modal" commandfor="newsletter-dialog" ``` `show-modal` is the declarative equivalent of calling `dialog.showModal()`. `close` is the declarative equivalent of calling `dialog.close()`. There is also `request-close`, which is closer to asking the dialog to close. It fires the cancel flow first, so code can prevent the close if needed. For simple demos, `close` is easier to understand. For serious confirmation flows, `request-close` can be a better fit. ## Control a popover without JavaScript Popover already had a declarative trigger: ```html ``` That still works. The difference is that `command` and `commandfor` are more general. They are not only for popovers. Here is the same menu with the new pattern: ```html ``` You can use: - `show-popover` - `hide-popover` - `toggle-popover` So the button can be explicit about what it does. A trigger button can toggle. A close button inside the popover can hide. A separate help button could only show. The nice part is that the browser still handles the native popover behavior. Auto popovers can light-dismiss. They can close with Escape. They live in the top layer instead of fighting the rest of your stacking context. If you want a deeper intro to the Popover API, I already wrote about [native HTML popovers](/short/html-popover-api/). This article is about the newer control layer on top. `commandfor` only handles the action. If the popover also needs to stay attached to its trigger, [CSS Anchor Positioning handles the placement](/blog/css-anchor-positioning-without-javascript/). To animate a dialog or popover as it enters the top layer, you can add an [`@starting-style` entry transition](/blog/starting-style-css/) without changing the HTML that opens it. You do not need to rewrite every popover just because `commandfor` exists. The older `popovertarget` syntax is still short and readable: ```html ``` For a simple popover trigger, that is perfectly fine. `commandfor` becomes more interesting when you want one mental model for different native actions: ```html ``` If all you need is a popover toggle, use the syntax your team finds clearer. If you are building a design system and want one declarative pattern for dialogs and popovers, `commandfor` is easier to standardize. ## Browser support This is the part you should not skip. `command` and `commandfor` are Baseline 2025 features. Support starts in Chrome and Edge 135, Firefox 144, and Safari/iOS Safari 26.2. Older browsers do not support them. That makes it reasonable for progressive enhancement, but you still need to think about the importance of the interaction. If a popover is just extra help text, unsupported browsers can miss the enhancement. If a modal is the only way to complete a critical checkout step, you need a fallback. The simplest fallback is still a tiny script: ```js if (!('command' in HTMLButtonElement.prototype)) { document.querySelectorAll('[command][commandfor]').forEach((button) => { const target = document.getElementById(button.getAttribute('commandfor')); const command = button.getAttribute('command'); button.addEventListener('click', () => { if (command === 'show-modal') target?.showModal?.(); if (command === 'close') target?.close?.(); if (command === 'toggle-popover') target?.togglePopover?.(); if (command === 'show-popover') target?.showPopover?.(); if (command === 'hide-popover') target?.hidePopover?.(); }); }); } ``` You may not need that fallback everywhere. But it is useful to understand the tradeoff: `commandfor` removes the JavaScript for modern browsers, and you can still add a small compatibility layer when the interaction is critical. ## Accessibility notes The biggest win here is not just fewer lines of code. It is fewer chances to rebuild native behavior incorrectly. With ``, the browser already understands modality. With popovers, the browser already understands top-layer placement and dismissal behavior. With buttons, the browser already understands keyboard activation. Still, you can make this worse if you ignore the basics: - use real ` ``` Here is the CSS: ```css .dropdown { overflow: hidden; height: 210px; /* example 1 */ height: auto; /* example 2 */ transition: height 0.5s ease; } .hide { height: 0px; } ``` Then I toggle the .hide class with some JS by clicking on the button. On the demo below you can see: - For the first one, the animation works because there is an explicit height. - For the second one, there are no animation because the height is set to auto. ## Transitioning the max-height in CSS Since transitioning height directly doesn’t work well, what about using max-height? Sadly, like the height value, you can't directly animate it's height to an auto value. This approach is still a bit better since you can set a larger value, and the animation will work. However, if the value is too high, the transition speeds up too quickly and becomes erratic. Here is the CSS: ```css .dropdown { overflow: hidden; max-height: 100px; /* | 400px | 1000px */ transition: height 0.5s ease; } .hide { max-height: 0px; } ``` Try toggling the content by clicking the button and changing the max-height values: You see that if the max-height has a value too big, the animation is too quick and delayed.
And if the value is too low, the text will be cropped. ## Height transition with flex The previous examples aren’t ideal, as they come with many limitations.
But did you know you can transition the height to 100% using flex?
The HTML structure changes a bit (there is one more div inside the dropdown): ```html ``` And the CSS looks like that: ```css .dropdown { display: flex; } .inner { max-height: 0; overflow: hidden; transition: max-height 0.5s linear; } .dropdown:not(.hide) .inner { max-height: 100%; } ``` See the example below: It works, but there are still two issues: - You need to add a wrapper div for it to function properly - While the content transitions, its container expands instantly ## Height transition with grid Another more flexible solution is using CSS Grid. Similar to the flex approach, Grid can help us transition an element’s height, but with better control and fewer limitations. In this case, we’ll utilize the grid-template-rows property to animate the height. Here’s the updated HTML: ```html ``` And the CSS: ```css .dropdown { grid-template-rows: 0fr; transition: grid-template-rows 0.5s ease; display: grid; } .inner { overflow: hidden; } .dropdown:not(.hide) { grid-template-rows: 1fr !important; } ``` In this example: - grid-template-rows: 0fr hides the content, while 1fr makes the content fully visible. - The height smoothly transitions as the grid adjusts. With Grid, there’s no need for extra wrapper divs, and the container height adjusts smoothly along with its content. It also gives you finer control over the layout compared to flexbox. ## New height transitions in the future? One promising feature is the upcoming calc-size() function, which will bring even more flexibility to CSS layouts. What this function does is convert values like auto to specific pixel values which it can then use in calculations with other values. This is handy on its own, but where it is most useful is with animating elements that are auto sized. Here is what it looks like: ```css .dropdown { height: 0; overflow: hidden; transition: height 0.3s; } .dropdown:not(.hide) { height: calc-size(auto); } ``` The support is almost inexistant as today, but I believe it will spread soon to all major browsers! ## Enjoyed this article? I write about modern CSS, HTML, and simpler ways to build for the web. Join my newsletter below if you want to learn modern CSS with practical tutorials like this. Happy coding! --- # How to Build a Dark Mode Toggle in Pure CSS URL: https://theosoti.com/blog/darkmode-css/ Published: 2024-09-30 Tags: CSS, modern CSS, accessibility, performance, CSS theming, JavaScript alternatives, frontend development > Build an accessible dark mode toggle with modern CSS, system preference support, and no JavaScript for the core theme switch. ## Introduction to darkmode In this step-by-step tutorial, I'll show you how to create a dark mode for your website using minimal JavaScript. You can follow along and see the results in real time with the interactive elements included. There are various ways to implement dark mode, ranging from simple to complex. Here, I'll walk you through a straightforward method that relies on three key principles: - **CSS variables** to easily switch color values between modes - A **checkbox** (or other input) to capture the user's preference - **localStorage** to make the selected mode persist across pages This is the same approach I've used on this site, so feel free to check it out! ## Setting up the HTML First things first, let's create a basic html template.
We'll need a **checkbox** and **some content**. ```html
``` We have our HTML structure, now let's set the colors. ## Add some CSS variables Let's declare **3 CSS variable** for now.
Put them in the **:root selector** so you are sure they are accessible everywhere.
We declare 1 for the text, 1 for the background and one for the links. ```css :root { --c-text: #493c37; --c-background: #eddfe0; --c-primary: #b392f0; } ``` Now let's link these variables to their elements. ```css h1, p { color: var(--c-text); } main { background-color: var(--c-background); } a { color: var(--c-primary); } ``` From now on, when you change the value of the CSS variables, it will impact all the elements linked to it. You can try and change the color values in the interactive example below. Now we have our HTML and CSS variables set up, but we still miss the main part: the darkmode.
It's pretty simple from here. We have to define the colors we want when we enter the darkmode. We also have to define when we are in a darkmode. To know when we are in darkmode, we just have to see if the input is checked.
We can do that easily in CSS using the relatively new selector **:has()**.
Here we watch if the **:root** element **has** the **input** (#demo-darkmode) in a **checked** state. If it's the case, we reassign the CSS variable with new values. ```css /* Default variables values */ :root { --c-text: #493c37; --c-background: #eddfe0; --c-primary: #b392f0; } /* Variables values when the input is checked */ :root:has(#demo-darkmode:checked) { --c-text: #fff; --c-background: #333; --c-primary: #98fb98; } ``` With just that addition, clicking the darkmode checkbox will switch the declared colors.
You can try it out in the interactive demo below. Our darkmode is working 🎉
Currently, if we check the darkmode checkbox and reload the page, it will reset to its original state. To fix this, we can make the darkmode persistant ## Add a localStorage Adding localStorage to maintain darkmode is pretty straightforward. We watch the checkbox, and when it changes state, we save it in a localStorage item. We give this item a name (here "demo-darkmode"), and the state of the checkbox (if it’s checked or not).
On the page load, we also modify the checkbox state by applying the localStorage item value. ```js const checkbox = document.querySelector('#demo-darkmode'); // When the checkbox state change, we store its state in the localStorage checkbox.addEventListener('change', function (event) { localStorage.setItem('demo-darkmode', event.currentTarget.checked); }); // Change the checkbox state depending of the value stored in the localStorage checkbox.checked = localStorage.getItem('demo-darkmode') === 'true'; ``` If you check the checkbox below and reload the page, the interactive demo should stay in darkmode. In less than 5 lines of JavaScript and 20 lines of CSS, we managed to create a darkmode.
It's functional, easy to implement, easy to maintain, and supported by over 88% of browsers. Want to see all the code in one place ? Check out [the CodePen](https://codepen.io/theosoti/pen/bGPpvZg) I created.

## What's next? There are still a lot of ways to improve the darkmode from here: - Implement **prefers-color-scheme**, which will automatically apply the user's preferred mode based on their system settings. - Add animations to make the transition smoother - Add more than just a light and dark mode - Change other values than just colors It's up to you to explore and decide how you want to enhance your dark mode! ## Enjoyed this article? I write about modern CSS, HTML, and simpler ways to build for the web. Join my newsletter below if you want to learn modern CSS with practical tutorials like this. Happy coding! ================================================================ SHORT POSTS ================================================================ # Add alternative text to CSS generated images URL: https://theosoti.com/short/accessible-css-generated-images/ Published: 2026-08-09 Tags: CSS, accessibility, generated content, alternative text, screen readers > Use the slash syntax of the CSS content property to provide replacement text for generated images while keeping meaningful content in HTML. Before and after code examples showing alternative text added after a slash in the CSS content property ## CSS generated images can have replacement text. Most people use `content` to insert icons or decorative images. But what if that generated image carries information? The `content` property accepts alternative text after a slash: ```css .element::before { content: url('/images/status.jpg') / 'Current status: available'; } ``` The value after the slash is the replacement text associated with the generated content. This syntax is widely available in browsers, but assistive-technology behavior can still vary. For meaningful content, real HTML such as `…` remains the safer choice. Use CSS generated content for decoration or progressive enhancement, not for information that must always be announced. --- If you liked this tip, you might enjoy my guide "You Don't Need JavaScript", which contains more ways to build modern interfaces with HTML and CSS. You can find it at https://theosoti.com/you-dont-need-js/. --- # Build accessible accordions with details and summary URL: https://theosoti.com/short/accessible-details-accordion/ Published: 2026-08-09 Tags: HTML, CSS, accessibility, details, summary, JavaScript alternatives > Use the native details and summary elements for keyboard-friendly accordions, then add a smooth CSS Grid animation without replacing their semantics. An accordion opening and closing smoothly using the native details and summary elements ## Make smooth accordions without JavaScript. The useful part starts with the `
` and `` elements. They give you native disclosure semantics and keyboard interaction without recreating the component in JavaScript. But what about smooth animations? This is where CSS Grid can help. Instead of relying on a guessed `max-height`, place the accordion content inside a grid row and transition it between `0fr` and `1fr`. The closed state uses a row height of `0fr`. When the `
` element opens, the row changes to `1fr`. A child wrapper with `overflow: hidden` keeps the content clipped during the transition. Keep `` as the trigger. The animation should improve the native component, not replace its keyboard behavior or accessible name. Also respect reduced-motion preferences when adding the transition. --- If you liked this tip, you might enjoy my guide "You Don't Need JavaScript", which contains more ways to build modern interfaces with HTML and CSS. You can find it at https://theosoti.com/you-dont-need-js/. --- # Create reusable CSS logic with @function URL: https://theosoti.com/short/css-custom-functions/ Published: 2026-08-09 Tags: CSS, custom CSS functions, experimental CSS, modern CSS, CSS tip > Define reusable custom CSS functions for calculations, colors, and fluid values without repeating the same expressions across a stylesheet. ## You can now write custom functions in CSS. You can keep reusable functions in a `functions.css` or `utils.css` file, much like a `utils.js` file alongside your `reset.css`. Here is what that can look like: ```css /* Return the negative of any value */ @function --negate(--value) { result: calc(-1 * var(--value)); } /* Return a color with a custom alpha value */ @function --opacity(--color, --opacity) { result: rgb(from var(--color) r g b / var(--opacity)); } /* Return a fluid font-size value */ @function --fluid-type(--font-min, --font-max, --type: 'header') { --scalar: if( style(--type: 'header'): 4vw; style(--type: 'copy'): 0.5vw ); result: clamp( var(--font-min), var(--scalar) + var(--font-min), var(--font-max) ); } ``` The function name starts with `--`, parameters behave like local custom properties, and `result` defines the returned value. `@function` is still experimental and has limited browser availability. Treat it as progressive enhancement or use it in a controlled browser environment for now. --- If you liked this tip, you might enjoy my guide "You Don't Need JavaScript", which contains more ways to build modern interfaces with HTML and CSS. You can find it at https://theosoti.com/you-dont-need-js/. --- # Replace six typography declarations with the CSS font shorthand URL: https://theosoti.com/short/css-font-shorthand/ Published: 2026-08-09 Tags: CSS, typography, CSS shorthand, font, CSS tip > Combine font style, variant, weight, size, line height, and family in one font declaration, while avoiding the shorthand reset trap. ## CSS can be verbose and repetitive. There are often several ways to declare the same result. Horizontal margins alone can be written in at least four ways: ```css /* Longhand */ margin-left: 10px; margin-right: 10px; /* Four-value shorthand */ margin: 0 10px 0 10px; /* Two-value shorthand */ margin: 0 10px; /* Logical shorthand */ margin-inline: 10px; ``` The `font` shorthand can combine six typography declarations in one line. ```css .title { font-style: italic; font-variant: small-caps; font-weight: 700; font-size: 1rem; line-height: 1.5; font-family: system-ui, sans-serif; } ``` Becomes: ```css .title { font: italic small-caps 700 1rem/1.5 system-ui, sans-serif; } ``` `font-size` and `font-family` are required. The other values are optional, but there is one catch: omitted font longhands are reset to their initial values. Use the shorthand when you intend to define the complete font state, not when you only want to change one typography property. --- If you liked this tip, you might enjoy my guide "You Don't Need JavaScript", which contains more ways to build modern interfaces with HTML and CSS. You can find it at https://theosoti.com/you-dont-need-js/. --- # Animate elements along a path with CSS Motion Path URL: https://theosoti.com/short/css-motion-path/ Published: 2026-08-09 Tags: CSS, CSS animation, motion path, offset-path, performance > Use offset-path, offset-distance, and offset-rotate to move an element along a curve or custom SVG path without animating left and top. An element following a curved route created with CSS Motion Path ## Make elements follow a path without JavaScript or GSAP. CSS Motion Path lets you animate an element along a curve, loop, or custom shape with three properties. `offset-path` defines the route. You can use a `path()` value or a basic shape. `offset-distance` places the element between the start and end of that route. `offset-rotate: auto` keeps the element aligned with the direction of travel. ```css .traveller { offset-path: path('M 20 100 C 80 0 220 0 280 100'); offset-rotate: auto; animation: follow-path 3s linear infinite; } @keyframes follow-path { to { offset-distance: 100%; } } @media (prefers-reduced-motion: reduce) { .traveller { animation: none; } } ``` An inline SVG can display the same path as a visual guide, while CSS controls the moving element. Motion paths work well for loaders, diagrams, timelines, and small interface details. They also avoid animating layout properties such as `left` and `top`. --- If you liked this tip, you might enjoy my guide "You Don't Need JavaScript", which contains more ways to build modern interfaces with HTML and CSS. You can find it at https://theosoti.com/you-dont-need-js/. --- # Build dark mode with CSS only URL: https://theosoti.com/short/css-only-dark-mode/ Published: 2026-08-09 Tags: CSS, dark mode, prefers-color-scheme, light-dark, JavaScript alternatives > Create a CSS-only dark mode with a checkbox and :has(), follow the system preference, or let light-dark() select theme-aware colors. A website switching between light and dark themes using only HTML and CSS ## You can build a dark mode toggle with only CSS. Combine an ``, CSS variables, and `:has()` to change a page theme without a JavaScript listener. ```css :root { --background: white; --text: #161616; } :root:has(#dark-mode:checked) { color-scheme: dark; --background: #161616; --text: white; } body { background: var(--background); color: var(--text); } ``` The checkbox acts as the trigger. When it is checked, `:has()` updates the variables and the whole interface follows them. CSS also gives you two other options. Use `prefers-color-scheme` to follow the user's system setting: ```css @media (prefers-color-scheme: dark) { body { background: #161616; color: white; } } ``` Or let `light-dark()` choose between two colors: ```css :root { color-scheme: light dark; } body { background: light-dark(white, #161616); color: light-dark(#161616, white); } ``` The CSS-only checkbox option does not persist the user's choice after navigation or reload. Use JavaScript only if you need to save that preference. --- If you liked this tip, you might enjoy my guide "You Don't Need JavaScript", which contains more ways to build modern interfaces with HTML and CSS. You can find it at https://theosoti.com/you-dont-need-js/. --- # Apply hover styles only when a device can hover URL: https://theosoti.com/short/hover-media-query/ Published: 2026-08-09 Tags: CSS, media queries, hover, touch interfaces, accessibility > Use the CSS hover media feature to avoid mouse-only interactions on touchscreens and provide appropriate behavior for each input type. ## Ever hovered on a touchscreen? Me neither. But your CSS might still assume that every user can hover. The `hover` media feature lets you apply styles only when the primary input device can hover conveniently: ```css @media (hover: hover) { .card:hover { transform: translateY(-0.25rem); } } ``` You can also target primary input devices that cannot hover: ```css @media (hover: none) { .tooltip-trigger { /* Keep the information available on tap or focus */ } } ``` This is useful for dropdowns, tooltips, hover transitions, and any interaction that would otherwise disappear on touchscreens. Use `any-hover` when you care whether any connected input can hover, not just the primary one. A touchscreen laptop with a mouse is a common example. Hover should remain an enhancement. Important actions and information still need to work with touch and keyboard input. --- If you liked this tip, you might enjoy my guide "You Don't Need JavaScript", which contains more ways to build modern interfaces with HTML and CSS. You can find it at https://theosoti.com/you-dont-need-js/. --- # Understand every CSS object-fit value URL: https://theosoti.com/short/object-fit-values/ Published: 2026-08-09 Tags: CSS, object-fit, responsive images, video, CSS tip > Control how images and videos fill a fixed media box with the five object-fit values: fill, contain, cover, none, and scale-down. The same image displayed with each of the five CSS object-fit values ## Master object-fit in under one minute. Images and videos do not always scale the way you want inside a fixed-size box. That is what `object-fit` controls. ```css .media { inline-size: 20rem; block-size: 12rem; object-fit: cover; } ``` There are five values: - `fill` stretches the media to fill the box, even when that changes its aspect ratio. - `contain` shows the whole media without cropping, which may leave empty space. - `cover` fills the box while preserving the aspect ratio, so some content may be cropped. - `none` keeps the media at its natural size. - `scale-down` chooses whichever result is smaller between `none` and `contain`. The element needs a constrained content box before the differences become visible. Pair `object-fit` with an explicit inline size, block size, or `aspect-ratio`. I use `object-fit: cover` most often for cards and thumbnails, where filling the frame matters more than showing every edge of the image. --- If you liked this tip, you might enjoy my guide "You Don't Need JavaScript", which contains more ways to build modern interfaces with HTML and CSS. You can find it at https://theosoti.com/you-dont-need-js/. --- # Transition an element to its natural height in CSS URL: https://theosoti.com/short/smooth-height-transitions/ Published: 2026-08-09 Tags: CSS, CSS transitions, CSS Grid, interpolate-size, calc-size, modern CSS > Compare the practical CSS techniques for animating dynamic height, including Grid, interpolate-size, and calc-size(), without guessing max-height. Several CSS techniques animating an element between its closed and natural height ## Smooth height transitions are possible in CSS. Animating from `height: 0` to `height: auto` has traditionally been awkward because the browser could not interpolate an intrinsic size. There are several ways to handle it: - A fixed `max-height`, which is simple but requires a guessed upper limit. - A transform, which looks smooth but does not move surrounding content. - CSS Grid with a row transitioning from `0fr` to `1fr`. - `interpolate-size: allow-keywords`, which allows transitions to intrinsic sizing keywords such as `auto`. - `calc-size()`, which lets calculations work with intrinsic sizes. The Grid method is the most practical fallback when surrounding content needs to move. `interpolate-size` and `calc-size()` are cleaner, but they still need progressive enhancement because browser availability is limited. I cover all five approaches with interactive examples in the full article: https://theosoti.com/blog/height-transition/ --- If you liked this tip, you might enjoy my guide "You Don't Need JavaScript", which contains more ways to build modern interfaces with HTML and CSS. You can find it at https://theosoti.com/you-dont-need-js/. --- # Build an animated perspective grid in pure CSS. URL: https://theosoti.com/short/moving-grid/ Published: 2026-05-26 Tags: CSS, CSS animation, CSS gradients, CSS tip > Layer two linear gradients, tilt with a 3D perspective transform, fade with a mask, and animate the background position. No JS, no images, just CSS. ## You can create an animated perspective grid in CSS. Here’s the idea: First, draw the grid with 2 linear gradients. One for the horizontal lines. One for the vertical ones. ```css .el { background-image: linear-gradient(white 2px, transparent 2px), linear-gradient(90deg, white 2px, transparent 2px); background-size: 50px 50px; } ``` That gives you a simple square grid. Then tilt it in 3D: transform: perspective(900px) rotateX(50deg); transform-origin: center; Now it starts to look like a floor. To make it fade naturally into the distance, add a mask: ```css .el { mask-image: linear-gradient(to bottom, rgb(0 0 0 / 0), rgb(0 0 0 / 0.3)); -webkit-mask-image: linear-gradient(to bottom, rgb(0 0 0 / 0), rgb(0 0 0 / 0.3)); } ``` And finally, animate the background position: ```css @keyframes moveGrid { from { background-position: center 0; } to { background-position: center 100px; } } ``` That’s what creates the movement. The lines themselves do not move as elements. You just shift the background pattern. So the full effect is: - 2 gradients for the grid - a perspective transform for depth - a mask for atmosphere - and a background animation for motion A nice example of how far simple layers can go. --- If you liked this tip, you might enjoy my guide "You don't need JavaScript", which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # You can now style browser find-in-page matches in CSS. URL: https://theosoti.com/short/css-search-text-pseudo-element/ Published: 2026-04-28 Tags: CSS, modern CSS, CSS typography, browser APIs, experimental CSS, progressive enhancement, CSS tip > Style browser find-in-page matches with CSS ::search-text and :current. Support is limited, so treat it as progressive enhancement. Illustration of customised search matches on a webpage, styled with the CSS ::search-text. ## You can now style text that matches a browser find-in-page search, in CSS with `::search-text`. That means when someone uses the browser search bar and looks for a word on your page, you can control how those matches appear. And it gets even better. You can use `::search-text` on its own to style all search matches on the page, or scope it to a specific area like a `section`. You can also combine it with `:current` to style the currently focused match differently from the others. ```css /* Style every match on the page */ ::search-text { background: oklch(0.9 0.15 90); color: black; } /* Style only the currently focused match */ ::search-text:current { background: oklch(0.7 0.2 30); color: white; } ``` This is one of those small features that feels very "platform-first". The browser already knows what text is being searched. Now CSS can react to it too. It opens the door to nicer reading experiences, better documentation pages, and more polished long-form content without building a custom search UI. That part is more of a design implication, but it is exactly the kind of use case this selector points to. Browser support is still limited though. [Can I Use](https://caniuse.com/) shows support in Chrome 144+, Edge 144+, and Opera 122+, while Firefox and Safari do not support it for now. So this is clearly progressive enhancement for now. But it is another nice example of CSS getting access to more real browser states. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # You can now create true randomness in CSS. URL: https://theosoti.com/short/css-random-function/ Published: 2026-04-26 Tags: CSS, modern CSS, experimental CSS, CSS tip > CSS random() lets you generate values inside a range and share random base values across properties. Powerful idea, but support is still limited. Illustration of stars displayed in random size and position, powered by CSS random() function. ## You can now create true randomness in CSS. With `random()` function. Just CSS picking a value inside a range. That means you can randomise things like size, position, angle, delay, and more. And the interesting part is this: It’s not just chaos. `random()` can also share a random base value. So multiple properties can stay connected. For example: the same random value for width and height, or a consistent variation across several elements. That makes it much more useful than a simple visual gimmick. It opens the door to more generative UI, more playful layouts, and less repetitive styling. But browser support is still very limited. MDN marks it as experimental and not Baseline. Right now, support is mainly in Safari 26.2+ and iOS Safari 26.2+. No Chrome, no Edge, no Firefox for now. So this is clearly not something to rely on in production yet. But it’s a very fun glimpse of where CSS is going. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # HTML in Canvas API Explained: Render HTML Inside Canvas URL: https://theosoti.com/short/html-in-canvas-api/ Published: 2026-04-18 Tags: accessibility, Canvas API, experimental web APIs, HTML, progressive enhancement, web development tip > Explore the experimental HTML in Canvas API, which renders real HTML inside canvas while preserving DOM interaction and accessibility. Demo of HTML rendered inside a canvas element showing interactive UI within a graphics context. ## The web is getting a bit weird again. And I mean that in a good way. There’s a new experimental API called HTML in Canvas. The idea is wild: You can render real HTML inside a `` surface, while keeping the DOM in sync for things like interaction and accessibility. It is currently a proposal, and Chromium has an implementation behind a flag. So instead of canvas being just pixels, it can start behaving more like a place where actual web content lives. The proposal describes three main pieces: - an opt-in attribute for `` - a 2D API to draw DOM content - a WebGL path to use that HTML as a texture. It could open the door to things like: HTML-mapped 3D screens, interactive UI inside graphics-heavy scenes, and more experimental interfaces without rebuilding everything manually in canvas. That is exactly the kind of direction shown in the current demos and explainer. It’s still very early. But definitely something worth watching. It kind of feels like the web is becoming playful again. Here's a few examples of what can be possible with this HTML in Canvas API: - https://x.com/jaffathecake/status/2039632975831191858 - https://x.com/mattrothenberg/status/2040416074710102300 - https://www.linkedin.com/posts/vittorio-retrivi_hear-me-out-wicg-is-experimenting-ugcPost-7447070535492071424-vygx - https://www.youtube.com/shorts/7LiJIpf8jD0 --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Shrink a sticky header only when it sticks. In CSS. URL: https://theosoti.com/short/shrink-sticky-header-scroll-state/ Published: 2026-03-16 Tags: CSS, modern CSS, scroll-driven animations, CSS animations, frontend development, CSS tip > Use CSS scroll-state queries to shrink a sticky header only after it becomes stuck. A cleaner way to build adaptive headers without scroll listeners. Illustration of a sticky header shrinking once it reaches the top, using CSS scroll-state stuck queries. ## Sticky headers can now shrink at the right moment. Only when they are actually stuck. And CSS can handle it on its own. The setup starts the same way: the sticky wrapper becomes a scroll-state container. ```css .header-container { position: sticky; top: 0; container-type: scroll-state; } ``` Once that is in place, you can style the \"stuck\" state directly. ```css @container scroll-state(stuck: top) { .header { padding-block: 0.75rem; box-shadow: 0 8px 20px rgb(0 0 0 / 0.1); } .header-title { font-size: 1.5rem; } } ``` This is a small detail, but it changes the feel of the interface. At first, the header can be roomy and expressive. Once it sticks, it can become tighter and more compact. That gives more space back to the content while keeping navigation visible. This pattern is useful on: - article pages - docs layouts - long landing pages - dashboards with persistent controls Before, this usually meant listening to scroll, calculating thresholds, and toggling classes at the right time. Now the browser exposes the exact state you actually care about: is the sticky element stuck or not? That makes the rule easier to explain too. The header stays large in its normal flow state, then becomes compact only after the sticky state is real. That makes the intent much clearer. You are not styling \"some scroll position\". You are styling a real layout state. It also means transitions become easier to reason about. The header can animate its padding, font size, shadow, or background as soon as the sticky state changes. This is also better for maintenance. The sticky logic stays in CSS next to the visual change, instead of being split between layout styles and a scroll script. That is the kind of improvement modern CSS is getting really good at. Not flashy. Just useful. More behavior in the platform. Less custom scroll glue. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # You can now detect when sticky is actually stuck. In CSS. URL: https://theosoti.com/short/sticky-stuck-scroll-state/ Published: 2026-03-15 Tags: CSS, modern CSS, scroll-driven animations, CSS animations, performance, JavaScript alternatives, CSS tip > Use CSS scroll-state queries to style a sticky element only when it is actually stuck to the top. No scroll listener, no class toggle, no JavaScript. Illustration of a sticky header changing style only when it becomes stuck, powered by CSS scroll-state. ## You can now detect when a sticky element is actually... stuck. In CSS. No scroll listener. No class toggle. No JavaScript. Here is the idea: you turn the sticky wrapper into a scroll-state container. ```css .header-container { position: sticky; top: 0; container-type: scroll-state; } ``` Then CSS can ask a very useful question: \"Is this element currently stuck to the top?\" ```css @container scroll-state(stuck: top) { .header { box-shadow: 0 8px 30px rgb(0 0 0 / 0.12); } .title { font-size: 1.5rem; } } ``` So when the header reaches the top and actually becomes sticky, its style changes automatically. In this kind of example: - the header gets a shadow - the title becomes smaller - transitions can smooth the whole change That means the header can feel more alive without any scroll logic in JavaScript. This solves a very common UI need. Designers often want one visual state before the header sticks, and another one after it locks to the top. Before `scroll-state`, that usually meant watching scroll position, toggling a class, and keeping logic in sync with layout. Now the browser can expose that state directly to CSS. That means the visual change happens for the real sticky moment, not for a rough threshold you had to maintain in JS. That is why this feature is so interesting. It removes the glue code around a behavior the platform already understands. It is especially useful for documentation layouts, long-form articles, dashboards, and sticky section bars. Anywhere a header changes role once it reaches the top, this becomes a clean native hook. Support is still experimental, so treat it as progressive enhancement. But as a platform feature, it is a very strong step forward. Less code. Less coordination between CSS and JS. More behavior described where the styling already lives. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Your slider can now know the active card. In CSS. URL: https://theosoti.com/short/scroll-state-active-slider/ Published: 2026-03-14 Tags: CSS, modern CSS, scroll-driven animations, CSS animations, JavaScript alternatives, CSS tip > Use CSS scroll-state queries to detect the snapped slide in a carousel and style the active card without Intersection Observer, scroll listeners, or JavaScript. Illustration of a snapped slider where the active card is styled differently with CSS scroll-state. ## Your slider can now know which card is active. In CSS. No Intersection Observer. No scroll listener. No JavaScript state tracking. Just `scroll-state`. In this pattern, each slide becomes its own scroll-state container. ```css .horizontal-track li { container-type: scroll-state; } ``` Then CSS can detect when one slide is the snapped one in the inline direction. ```css @container scroll-state(snapped: inline) { .card-content img { filter: sepia(0) brightness(1); transform: scale(1.1); } .card-footer { background-color: var(--bg-color); color: var(--color-orange); } .num { opacity: 1; color: var(--color-orange); } } ``` So when a card becomes the snapped item: - the image gets highlighted - the footer changes style - the number becomes visible All automatically. That is the interesting part. The browser already knows which slide is snapped. Now CSS can react to that state directly. This makes sliders feel much smarter without adding JavaScript just to figure out which card is active. You can keep the behavior in the platform and keep your component logic simpler. It is a very good fit for carousels, product galleries, onboarding steppers, and horizontal story-like interfaces. Anywhere snap points already exist, the styling can follow naturally. It also makes the styling more trustworthy. You are reacting to the exact card the browser considers snapped, not to an approximation you had to detect yourself. What makes `scroll-state` exciting is that it reacts to real interaction states: - stuck - scrollable - snapped That is a big shift. CSS is not only styling static elements anymore. It is starting to respond to what the browser already knows about scrolling behavior. Support is still emerging, so this is best treated as a progressive enhancement for now. But the direction is very clear. Less glue code. Less bookkeeping. More UI behavior handled directly in CSS. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Reset Form Inputs Natively with button type="reset" URL: https://theosoti.com/short/reset-form-natively/ Published: 2026-03-07 Tags: HTML forms, performance, HTML, JavaScript alternatives, web development tip > Use the native reset button to restore inputs, selects, checkboxes, and validation state without JavaScript. A simple HTML feature that is still easy to forget. Illustration of a form with submit and reset buttons, showing native valid and invalid form states. ## You can reset a form natively. No helper function. No state reset logic. No JavaScript at all. HTML already gives you a reset button. ```html
``` When the reset button is clicked, the browser restores every field to its initial value. That includes: - text inputs - textareas - selects - checkboxes - radios It also resets states tied to the original values. That is why it is useful in demos, filter panels, and small utility forms. If your inputs start empty, reset brings them back to empty. If a checkbox starts checked in the HTML, reset restores that checked state. The browser simply goes back to the form's original snapshot. Another nice detail: validation states follow that reset too. If a field became invalid after typing, resetting the form brings the field back to its starting value and clears that temporary state. This is where the feature becomes practical. You do not need custom code to clear multiple fields one by one. The browser already knows what the initial state was. There is also a small mental shift here. Reset does not mean "empty everything". It means "go back to the original HTML values". That distinction matters when a form starts with defaults, preselected options, or checked filters. That said, use it with intent. A reset button can be frustrating in long forms if users click it by mistake. It works best when clearing the form is a real, expected action. Good examples: - search forms - filter sidebars - playground demos - short admin tools Less good examples: - long checkout flows - multi-step forms - anything where accidental clearing would be costly So the feature is not new. But it is still underrated. Sometimes the fastest solution is already built into HTML. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Fix z-index leaks with CSS isolation: isolate URL: https://theosoti.com/short/isolation-isolate-property/ Published: 2026-03-03 Tags: CSS, modern CSS, CSS typography, CSS tip > Use isolation: isolate to create a new stacking context and keep pseudo-elements, blend effects, and negative z-index layers inside their component. Illustration comparing a card without isolation isolate and a card with isolation isolate, showing decorative quote marks staying inside the component. ## `isolation: isolate` can save your layering. Especially when decorative elements start escaping their card. This property creates a new stacking context. ```css .card { position: relative; isolation: isolate; } ``` That sounds technical. But the effect is simple. It tells the browser: \"everything inside this component should stack inside this component.\" This is very useful when you add: - pseudo-elements - blend effects - negative z-index layers - decorative backgrounds Without isolation, a child with `z-index: -1` can slip behind other elements outside the card. Sometimes it disappears behind the page. Sometimes it overlaps in places you did not expect. With `isolation: isolate`, the parent becomes its own stacking world. Your decorative layer can sit behind the content, while still staying inside the component. That is why this property pairs so well with `::before` and `::after`. ```css .card { position: relative; isolation: isolate; } .card::after { content: ''; position: absolute; inset: 0; z-index: -1; } ``` This is also useful with `mix-blend-mode`. Blend effects can interact with everything behind them. Isolation limits that interaction to the component itself, which makes the result much easier to control. That is why the property feels so useful in modern UI work. Cards, badges, highlights, blurred layers, and oversized quote marks often rely on decorative elements behind the content. `isolation: isolate` gives those effects a safe boundary. The key idea is this: `isolation` does not move anything. It does not replace `position`. It does not replace `z-index`. It simply gives the component a boundary for stacking. So if one of your fancy background shapes keeps leaking outside its card, this is probably the missing piece. Small property. Very practical fix. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # React to Scroll with CSS Scroll State Queries URL: https://theosoti.com/short/scroll-state-query/ Published: 2026-02-11 Tags: CSS, modern CSS, scroll-driven animations, CSS animations, performance, JavaScript alternatives, CSS tip > Use CSS scroll state queries to style elements based on scroll position. Detect top, bottom, or scrolling states without JavaScript or scroll listeners. ## Let CSS know when you scroll. No JavaScript needed. Scroll-state queries let you style elements based on how a container is scrolled. You can detect when it’s at the top, bottom, or in motion, and trigger animations directly from CSS. Here’s a simple example: ```css html { container-type: scroll-state; container-name: scroller; } .to-top { position: fixed; bottom: 20px; right: 20px; translate: 80px 0; transition: translate 0.4s ease; } @container scroller scroll-state(scrollable: top) { .to-top { translate: 0 0; } } ``` The key is `container-type: scroll-state`. It makes the page a scroll container that CSS can observe. The `@container` rule reacts to its scroll state. When the container is at the top, bottom, or being scrolled, styles update automatically. No scroll listeners. No intersection observers. Just CSS responding to motion. You can use it to fade headers, slide elements, or trigger transitions as users scroll. Browser support is about 68.5%, so it’s still experimental, use with caution (or don't use it yet). This is a glimpse of a more reactive CSS. Less scripting, more design freedom. Scroll state queries are practical for sticky headers, section indicators, and context-aware controls. Because the state is declarative in CSS, you can remove a lot of JS scroll bookkeeping and keep behavior easier to maintain. Scroll state queries are practical for sticky headers and section-aware controls. Since state logic stays in CSS, you can reduce JS scroll bookkeeping and keep behavior easier to audit. Always include reduced-motion behavior. A strong animation pattern is one that degrades cleanly while preserving meaning. For teams, `container-type: scroll-state` is easiest to maintain when it starts in one documented example before broader reuse. This keeps implementation predictable and prevents style drift as the codebase grows. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Align Numbers Perfectly with CSS tabular-nums URL: https://theosoti.com/short/tabular-nums/ Published: 2026-01-26 Tags: CSS, modern CSS, CSS layout, responsive design, CSS typography, frontend development, CSS tip > Use CSS tabular-nums to align numbers in tables, dashboards, prices, and stats for cleaner, more readable interfaces and layouts. ## Ever noticed how numbers often look misaligned? That's because most fonts use proportional numbers by default. Each digit takes up a different amount of space. But there's a fix for that. In CSS, you can opt in to tabular numbers, where all digits have the same width. That makes columns of numbers line up perfectly. Here's how to enable it: ```css .after { font-variant-numeric: tabular-nums; font-feature-settings: 'tnum'; } ``` This works best with fonts that support OpenType features. But most system fonts and many web fonts do. Clearer layouts, cleaner data tables, and a subtle polish your UI users will feel. Use tabular figures only where alignment matters: prices, analytics, timers, and table columns. For body copy, proportional digits usually look better. A small utility class like `.tnum { font-variant-numeric: tabular-nums; }` keeps the pattern reusable. Also check decimal alignment and negative values like `-12.40`, so spacing remains stable when data updates in real time. Enable `tabular-nums` where alignment matters most, like prices, stats, and timers. Keep proportional figures in body text, and reserve fixed-width digits for data views where quick comparison is important. Enable `tabular-nums` where numeric comparison matters, like prices, stats, and timers. Keep proportional figures in body copy, then switch to fixed-width digits in data views for faster visual scanning. Test with multilingual strings and varied word lengths. Typographic CSS should remain graceful when content is unpredictable. For teams, `-12.40` is easiest to maintain when it starts in one documented example before broader reuse. That rollout sequence preserves clarity and reduces regressions during future refactors. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Create a Mirror Reflection Effect with CSS box-reflect URL: https://theosoti.com/short/reflexion-effect/ Published: 2026-01-20 Tags: CSS, modern CSS, progressive enhancement, CSS tip > Create a mirror reflection effect in CSS with -webkit-box-reflect and add glossy image reflections with a single property and graceful fallback. ## You can create a mirror reflection effect in CSS. With one property. No pseudo-elements. No duplicated images. No gradients hacks. ```css img { -webkit-box-reflect: below -5px linear-gradient(transparent, rgba(0, 0, 0, 0.5)); } ``` Here’s what’s happening: `-webkit-box-reflect` does 3 things: - below: puts the reflection under the element (you can also use above, left, right) - -5px: the gap between the image and its reflection - linear-gradient(…): fades the reflection out nicely It looks great for: - product shots - hero images - galleries - “glass” / glossy UI vibes ⚠️ Small catch: it’s a WebKit-only feature. Works in Safari (and usually Chromium), but not Firefox. Still, it’s a fun trick when you can use progressive enhancement. `box-reflect` works best as a subtle accent instead of a full mirror. Keep the reflection short and softly faded, so the visual effect supports hierarchy without competing with the main content. Here’s what’s happening: ` is most useful when you apply it with a narrow scope and a clear component-level objective. For this article, the strongest use case is straightforward: the technique is small, but the maintenance benefit grows when the team applies it with clear intent. Before shipping, keep this checklist in mind: keep fallback behavior explicit so unsupported environments remain stable. As always, test with real content and realistic viewport ranges; that is the fastest way to confirm the pattern holds up outside sandbox demos. Here’s what’s happening: ` is intentional usage: one focused rule can often replace several brittle overrides. In this exact topic, the implementation payoff is clear: this is a good example of modern css reducing complexity while keeping behavior declarative. One thing worth validating early: document a short usage rule to keep future edits predictable. If you document this as a team convention, future refactors become easier because everyone understands when the technique should, and should not, be used. `box-reflect` works best as a subtle visual accent on hero media or decorative cards. Keep reflections short and faded, so the effect supports hierarchy without stealing attention from primary content. Validate it with real content and realistic viewport ranges so implementation details hold up outside demos. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # CSS corner-shape: Shape Corners Beyond border-radius URL: https://theosoti.com/short/corner-shape/ Published: 2026-01-19 Tags: CSS, modern CSS, CSS shapes, frontend development, progressive enhancement, CSS tip > Learn the CSS corner-shape property to create scoop, bevel, squircle, and custom corners beyond border-radius with progressive enhancement. ## You can now shape corners in CSS. Not just round them. Meet corner-shape. border-radius only changes how much rounding you get. corner-shape changes the geometry of the corner itself. Same element. Same radius. Different corner style. Square. Rounded. Scoop. Bevel. And more. This matters because corners carry tone. They can feel softer. Or sharper. More designed. More intentional. Great for buttons, cards, and UI chrome. Browser support is the catch. Around 68.65% today. No Safari. No Firefox. So it’s not for critical UI yet. But it works well as progressive enhancement. Unsupported browsers simply fall back to normal corners. No breakage. No hacks. CSS is slowly giving us more control over geometry. Less SVG. Fewer pseudo-elements. More design system power. If you want predictable results, pair `corner-shape` with a regular `border-radius` fallback and gate advanced shapes with `@supports (corner-shape: bevel)`. That way, older browsers keep the rounded version while capable browsers get the sculpted corners. Try it on small components first: buttons, tags, and cards. Big radii with aggressive shapes can reduce readable surface area, so test with long labels and tighter mobile widths. Use `corner-shape` as a brand accent, not as a global default. Keep `border-radius` as your base token, then apply custom corner geometry to decorative surfaces like cards and badges where browser fallback remains graceful. Use `corner-shape` as a brand accent rather than a default everywhere. Keep `border-radius` as baseline, then apply custom geometry on selected components where fallback to rounded corners still looks intentional. Media-related CSS needs testing across different asset ratios and resolutions. Real image variance is where composition rules are truly validated. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Position Elements with CSS sibling-index() URL: https://theosoti.com/short/sibling-index/ Published: 2026-01-13 Tags: CSS, modern CSS, CSS layout, responsive design, HTML forms, CSS selectors, performance > Learn how CSS sibling-index() lets elements position themselves based on sibling order. Use pure CSS math for layouts without JavaScript or nth-child selectors. ## Place elements relative to their siblings. With one CSS function. Meet sibling-index(). It gives each element a number. Based on its position among siblings. That number can drive layout. No extra classes. No :nth-child() chains. No JavaScript. Here’s the trick I used to place dots in a circle. Each dot computes its own angle. ```css .dot { --angle: calc(sibling-index() * 9deg); --radius: 240px; position: absolute; width: 25px; height: 25px; transform: translate(-50%, -50%); top: calc(50% + sin(var(--angle)) * var(--radius)); left: calc(50% + cos(var(--angle)) * var(--radius)); } ``` Here’s what happens. sibling-index() returns the position of the dot. You multiply it by 9deg. That spreads 40 dots across a full circle. Then sin() and cos() convert the angle into coordinates. top uses sin(). left uses cos(). The circle is centered with 50%. The dot is centered with translate(-50%, -50%). Simple math. Pure CSS. Very little markup. Browser support is still limited. About 69.3% today. And there is no Firefox support yet. But the idea is powerful. CSS is getting real logic now. What would you build with sibling-index()? `sibling-index()` is excellent for staggered offsets and sequence-based styling without extra classes. Combined with `calc()`, it keeps patterns adaptive when list order changes or items are inserted dynamically. `sibling-index()` is great for sequence-aware styling without adding helper classes. It becomes especially useful for staggered layouts where item order can change and manual nth rules become fragile. A quick edge-case audit with inserted elements helps confirm that selector logic stays predictable over time. To roll this out safely, start by applying `sibling-index()` in a single UI surface where the benefit is obvious. After validation, expand gradually to matching components and avoid ad-hoc one-off overrides. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # When Relative Units Behave Differently: EM vs REM in CSS URL: https://theosoti.com/short/em-or-rem/ Published: 2026-01-10 Tags: CSS, modern CSS, CSS layout, responsive design, CSS typography, CSS tip > Learn the difference between CSS em and rem units, when to use each one, and how they affect scalable typography, spacing, and layouts. ## EM and REM look the same. Until they don’t. Both are relative units. But they reference different things. rem is always based on the root font size. Usually the html element. em is based on the computed font size of the element. Or its parent, depending on what you are sizing. Here’s the part that confuses people. em can compound. If you set font-size in em, the element’s font size changes. Then every em value inside that element scales from this new size. Padding. Margin. Gaps. Everything. rem does not compound. It always goes back to the root. A quick mental model. Use rem when you want consistency across the page. Typography scale. Spacing scale. Layout rhythm. Use em when you want a component to scale with its own text. Buttons. Chips. Badges. Cards with internal spacing tied to font size. Rule of thumb. rem for global rules. em for local rules. If you ever got a button that grows too much inside a container, it was probably em compounding. Do you default to rem everywhere? A reliable rule is simple: use `rem` when values should follow the root font size, and use `em` when values should scale with the component itself. That split keeps accessibility strong while nested components preserve their own rhythm. A practical rule is simple: use `rem` for global rhythm and accessibility scaling, and use `em` when sizing should follow the local component context. This prevents nesting surprises in reusable UI blocks. Keep fallback behavior explicit and straightforward. Stable defaults make advanced enhancements much safer to maintain. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Scroll-Driven Animations in CSS with @starting-style URL: https://theosoti.com/short/scroll-animation/ Published: 2025-12-20 Tags: CSS, modern CSS, scroll-driven animations, CSS animations, CSS tip > Learn how to combine entry animations and scroll-driven motion using modern CSS. Use @starting-style, custom properties, and scroll timelines. ## You can combine scroll-driven animations with `@starting-style` in CSS. The goal is simple. Fade elements in on page load. Then animate them based on scroll. At first glance, it sounds easy. In practice, scroll timelines behave differently from classic animations. So you need a small setup. ### Register a custom property Instead of animating opacity directly, animate a numeric value you control. ```css @property --progress { syntax: ''; inherits: false; initial-value: 0; } ``` This gives you a stable value to work with. ### Define the entry state Use `@starting-style` to handle the initial fade-in. ```css @starting-style { opacity: 0; } ``` Now the browser knows the element’s starting point, even if it appears dynamically. ### Drive the animation with scroll Animate the custom property using a scroll timeline. ```css animation: progress linear both; animation-timeline: view(); ``` Then map it back to real styles. ```css opacity: var(--progress); ``` That’s the pattern. Smooth entry animation. Scroll-linked motion. No hacks. No JavaScript. Just modern CSS doing modern UI work. This kind of setup shows how far CSS has evolved. And how much interaction it can handle on its own. Have you started experimenting with scroll-driven animations yet?! Scroll-driven animations should reinforce context, not distract from content. Keep progress-linked motion restrained and provide comfortable fallbacks for reduced-motion users. Run interaction tests with rapid clicks and navigation changes. Input should stay responsive even while transitions are active. A practical way to adopt `@starting-style` is to scope it to one high-impact component first. This keeps implementation predictable and prevents style drift as the codebase grows. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Build a Masonry Layout with Pure CSS column-width URL: https://theosoti.com/short/masonry-layout/ Published: 2025-10-04 Tags: CSS, modern CSS, CSS Grid, CSS layout, responsive design, performance, JavaScript alternatives > Create responsive Masonry layouts using the CSS column-width property. Fit flexible columns automatically, no JavaScript or grid hacks—just clean, native CSS. ## You only need a few CSS lines to build a Masonry layout. Here’s how it works: The magic happens with the column-width property. ```css .columns { width: 100%; column-width: 200px; gap: 1em; } ``` This tells the browser: “Fit as many 200px-wide columns as you can into this container.” If the screen is wider, more columns fit. On smaller screens, fewer columns show. The gap sets the horizontal spacing between them. The rest is just good styling: - Images set to width: 100% so they fill the column - object-fit: cover to keep them visually balanced - A bit of padding, rounded corners, and borders for aesthetics No position hacks. No flex/grid juggling. Just native CSS doing the heavy lifting. And it adapts instantly when you resize the container. Link to the codepen: https://codepen.io/theosoti/pen/ogXedrz Column-based masonry layouts are fast to implement for feeds and galleries. Always test reading flow and item ordering, because visual columns can differ from the underlying DOM sequence users navigate. Column-based masonry is quick for feeds, but always verify reading order and keyboard navigation. Visual placement should not conflict with logical DOM sequence. Before rollout, verify long content, dense cards, and extreme widths. Stable behavior under stress is what turns a neat trick into production CSS. When introducing `column-width`, begin with one reference component and treat it as the canonical pattern. Then reuse that same pattern in similar contexts so behavior stays consistent and review time stays low. If reading order matters, double-check keyboard and screen-reader flow so visual columns do not confuse content sequence. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Responsive Components with CSS Container Queries URL: https://theosoti.com/short/emoji-toggle-cq/ Published: 2025-09-20 Tags: CSS, modern CSS, container queries, responsive design, CSS layout, CSS tip > Use CSS container queries to make components respond to their parent’s size, not the viewport. Create adaptive layouts and icons with clean, modern CSS. ## Make components responsive to their parent. Not the viewport. In the demo, the icon changes based on the content's height. All with CSS container queries. Step 1 - declare a container. Set `container-type: size;` on the wrapper (optionally `container-name: box;`). Step 2 - define a default state. Use `.content:after { content: "default"; }` or your base icon. Step 3 - react to the container, not the page. Write `@container (height > 60px) { .content:after { content: "state-1"; } }`. Add another for a larger breakpoint, e.g. `(height > 240px)`. As the content grows or shrinks, the pseudo-element swaps automatically. No JavaScript. No viewport media queries. Works inside grids, flex layouts, and nested components. Support is solid in modern browsers with almost 93%. Ship a sensible default so it still looks fine where `@container` isn’t supported. Container-query-driven components are ideal for small interactive widgets that appear in many parent layouts. Defining behavior from local width keeps them responsive without page-level media-query coupling. Container-query-driven widgets stay reusable because they respond to local width, not page assumptions. This is ideal for components embedded in many parent contexts. Before rollout, verify long content, dense cards, and extreme widths. Stable behavior under stress is what turns a neat trick into production CSS. For teams, `container-type: size;` is easiest to maintain when it starts in one documented example before broader reuse. That rollout sequence preserves clarity and reduces regressions during future refactors. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Style Scrollbars with CSS scrollbar-color & scrollbar-width URL: https://theosoti.com/short/scrollbar-customisation/ Published: 2025-09-17 Tags: CSS, modern CSS, scroll-driven animations, CSS animations, CSS colors, accessibility, frontend development > Customize scrollbars to match your UI using scrollbar-color and scrollbar-width. Control colors, size, and accessibility. All with just two CSS properties. ## Make your scrollbars match your UI. Do it with only two CSS properties. `scrollbar-color `sets the colors. First value is the thumb. Second is the track. Example: `scrollbar-color: hashtag#8aaa8a hashtag#294b29;` `scrollbar-width` controls size. Use auto, thin, or none to hide it. Example: `scrollbar-width: thin;` Apply these on any scrollable container. Or put them on `:root to style the page scrollbars. It respects user settings like overlay scrollbars. So on macOS they appear when scrolling and keep your colors. Accessibility tip. Pick a thumb color with strong contrast against the track. For the browser support, it's not great for now. ~73% for the `scrollbar-color` property. ~83% for the `scrollbar-width` property. But they graciously fallback to default style if not supported. Custom scrollbars can improve visual consistency, but usability must stay first. Keep enough contrast and width for discoverability, and avoid styling choices that hide scroll affordance. Custom scrollbars can support visual consistency, but discoverability and contrast come first. Keep width and color choices readable across themes. Run interaction tests with rapid clicks and navigation changes. Input should stay responsive even while transitions are active. For teams, `scrollbar-color` is easiest to maintain when it starts in one documented example before broader reuse. That rollout sequence preserves clarity and reduces regressions during future refactors. Aim for subtle customization that keeps scrollbar track and thumb clearly distinguishable at a glance. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # CSS image-rendering: pixelated. Scale Pixel Art Without Blur URL: https://theosoti.com/short/crispy-images/ Published: 2025-09-15 Tags: CSS, modern CSS, CSS tip > Preserve sharp edges when scaling pixel art, icons, retro screenshots, or QR codes using one line of CSS supported by modern browsers. ## Scale pixel art without blur. Keep it crisp with one line. By default, browsers smooth images when you scale them, which makes sprites and icons look muddy. image-rendering: pixelated; forces nearest-neighbor scaling so every pixel stays sharp. Use it when you upscale low-res assets: pixel art, icons, retro screenshots, QR codes. Skip it for photos or illustrations that should stay smooth. Apply it to the element you’re resizing (img, canvas). It takes effect when the rendered size differs from the image’s intrinsic size. If you’re using background images, test in your target browsers since support can vary. No extra assets. Smaller downloads. Clean, crunchy results. Modern browser support is broad with ~95%. Don't hesitate to share projects where you used this property! `image-rendering: pixelated` is perfect for pixel art and retro assets that need hard edges when scaled. Apply it selectively, because photographs and gradients usually look degraded with this mode. `image-rendering: pixelated` preserves hard edges for pixel art and retro sprites. Use it selectively, since photos and gradients usually degrade with this mode. Keep fallback rendering in mind so unsupported effects still produce clear, readable visuals. When introducing `image-rendering: pixelated`, begin with one reference component and treat it as the canonical pattern. That rollout sequence preserves clarity and reduces regressions during future refactors. Reserve the pixelated mode for true pixel art assets, and keep other imagery on normal rendering to avoid accidental degradation. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Create Multi-Column Layouts with CSS Columns URL: https://theosoti.com/short/simple-columns/ Published: 2025-08-30 Tags: CSS, modern CSS, CSS layout, responsive design, frontend development, CSS tip > Build column layouts with the CSS columns property. Add spacing, separators, and spanning elements in just two lines of code. ## Did you know you can create a column layout without grid or flex? It only takes 2 lines of CSS and can be useful! One key advantage is the ability to easily add separators between columns. So how to implement it? Apply the `columns` property to your container. This property accepts up to two values: - The minimum width of a column (not mandatory) - The number of desired columns (mandatory) And just with that it works! But you can go further by: - Using the `gap` property to create spacing between columns. - Adding separator with the “column-rule” property (it works just like `border`). - Make an element span across all columns with the `column-span` property. Don’t hesitate to use it in your next project! CSS columns are effective for flowing long-form text with minimal setup. Tune column width and gaps carefully, because readability depends on line length as much as layout density. CSS columns are quick for flowing long-form text and lightweight galleries. Tune width and gaps carefully, because readability depends strongly on line length. Use this pattern to keep responsive behavior local to the component instead of scattering viewport overrides. That makes scaling and refactoring much safer. To roll this out safely, start by applying `column-span` in a single UI surface where the benefit is obvious. Then reuse that same pattern in similar contexts so behavior stays consistent and review time stays low. Before shipping, test `column-span` with both short and long content, then verify behavior in narrow and wide containers. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # CSS backdrop-filter: Frosted Glass Effect Made Easy URL: https://theosoti.com/short/glassomorphism-backdrop-filter/ Published: 2025-08-29 Tags: CSS, modern CSS, CSS effects, accessibility, frontend development, CSS tip > Enhance your UI with the CSS backdrop-filter property. Apply blur, brightness, or contrast for sleek frosted-glass effects. ## Want to give your UI a sleek, modern touch? Try the CSS backdrop-filter property. It lets you apply visual effects behind an element, like blur, brightness, or contrast. It's what creates that soft, frosted-glass look you see in modern UIs. Here’s how simple it is: ```css .card { backdrop-filter: blur(5px); } ``` That’s it. One line of CSS, and your element blends beautifully into whatever is behind it. You can combine multiple effects: blur(5px) brightness(1.2). Bonus: It’s widely supported in modern browsers, with more than 96% Have you tried backdrop-filter yet? Share your favorite use case or a creative twist below. For best results, pair `backdrop-filter` with a semi-transparent background (for example `rgb(255 255 255 / 0.2)`). The filter needs translucency to create that frosted effect visibly. Add a graceful fallback for unsupported environments: keep a solid or lightly transparent background so contrast stays readable even without blur. Decorative effects should never reduce legibility. Backdrop blur should support legibility, not reduce it. Combine `backdrop-filter` with translucent fills and subtle borders, then verify text contrast on busy backgrounds before shipping. Backdrop blur should support clarity, not reduce it. Combine it with translucent fills and borders, then verify text contrast on busy backgrounds. Test with multilingual strings and varied word lengths. Typographic CSS should remain graceful when content is unpredictable. A practical way to adopt `backdrop-filter` is to scope it to one high-impact component first. This keeps implementation predictable and prevents style drift as the codebase grows. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Crisp Background Images with CSS image-set() URL: https://theosoti.com/short/sharp-background-images/ Published: 2025-08-28 Tags: CSS, modern CSS, responsive design, CSS layout, CSS tip > Serve sharp, responsive backgrounds with CSS image-set(). Let browsers choose the right asset for retina or standard screens efficiently. ## Serve sharp backgrounds without extra code. Let the browser pick the right asset. `image-set()` is like `srcset`, but for CSS. You give multiple sources. The browser chooses the best one for the screen. Why use it: - Retina screens get crisp images. - Regular screens download smaller files. - No JavaScript. No hacks. It also works in other image-accepting properties like content, list-style-image, and border-image(but with less browser support). Using `image-set()` in a background-image property has almost 96% browser support! Practical tips: - Add a plain fallback first for older browsers: `background-image: url("evee.png");` then the `image-set()`. - Keep file names consistent (@1x, @2x) to simplify builds. - Pair with background-size: cover/contain as needed. - Test AVIF/WEBP plus JPEG/PNG as a safe fallback. `image-set()` helps serve crisp backgrounds on high-density screens without always downloading the heaviest asset. Add sensible fallbacks so lower-density devices still render quickly and clearly. `image-set()` helps deliver sharper backgrounds on high-density displays without always forcing large assets. Keep sensible fallbacks to protect load performance. Keep fallback rendering in mind so unsupported effects still produce clear, readable visuals. A practical way to adopt `image-set()` is to scope it to one high-impact component first. After validation, expand gradually to matching components and avoid ad-hoc one-off overrides. Pair `image-set()` with explicit `background-size` rules so high-density assets remain sharp without unexpected cropping. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Pure CSS Typing Animation Effect without JavaScript URL: https://theosoti.com/short/typewrite-effect/ Published: 2025-08-27 Tags: CSS, modern CSS, CSS animations, CSS typography, performance, JavaScript alternatives, frontend development > Create a smooth typing text animation using pure CSS with keyframes, steps(), overflow, and a blinking caret. No JavaScript required. ## Make your text type itself. With pure CSS. How it works - overflow: hidden hides the unrevealed text. - white-space: nowrap keeps everything on one line. - Animate width from 0 to 100% with @keyframes typing. - Use steps(n) to reveal characters one by one. Set n close to the character count for a crisp tick. - A 1px border-right acts as the caret. A second animation toggles its color to “blink”. - Add animation-fill-mode: forwards so the text stays visible at the end. Practical tips - Prefer a monospace font for perfectly even steps, or tune the steps() value if letters have different widths. - If the string changes, update the steps() count. - Respect users with @media (prefers-reduced-motion: reduce) to disable or simplify the effect. The best part? It's supported by almost 96% of browsers! Would you use this in a hero, headline, or code sample? A CSS typing effect works best for short phrases and compact hero copy. Keep timing consistent and provide immediate readable text when motion is reduced or unsupported. Typing effects are strongest on short, high-impact lines. Keep timing tight and provide immediate readable fallback when motion preferences disable animation. Typography refinements are most visible with real content length and mixed wording. Validate rhythm, spacing, and line breaks on mobile where small issues become obvious. When introducing `typing animation`, begin with one reference component and treat it as the canonical pattern. This keeps implementation predictable and prevents style drift as the codebase grows. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # CSS Moving Background Text Effect without JavaScript URL: https://theosoti.com/short/text-background-animation/ Published: 2025-08-19 Tags: CSS, modern CSS, CSS animations, CSS typography, performance, JavaScript alternatives, CSS tip > Learn how to animate text with a moving background using only CSS. A lightweight effect with background-clip, keyframes, and zero JavaScript. ## Text with a moving background? No JavaScript needed. This effect is pure CSS, and it's clean, fast, and surprisingly easy to pull off. We use: - background-image 👉 display the image - background-clip: text 👉 attach the image to the text - color: transparent 👉 make the text invisible (to see the image behind it) - A simple `@keyframes` animation That’s it. Just a few lines of code and your typography comes alive. - No libraries. - No DOM hacks. - No bloat. Perfect for hero sections, portfolios, landing pages, or even just showing off what CSS can do. It’s part of a growing mindset: Use less JS. Push CSS further. There’s a lot you can build without JavaScript, and it’s more fun than you think. Moving background text effects should prioritize readability over spectacle. Keep motion slow enough to avoid visual fatigue, and ensure the non-animated state still communicates the message clearly. Moving text backgrounds should never compromise legibility. Keep motion slow and contrast strong, and ensure a clear static state when animation is reduced. Use this as a readability tool, not only a visual effect. The best result is when style improves scanning speed without adding cognitive load. A practical way to adopt `@keyframes` is to scope it to one high-impact component first. After validation, expand gradually to matching components and avoid ad-hoc one-off overrides. Before final rollout, validate `@keyframes` against real content and real interaction states, not only demo text. Always verify readability when animation is paused, because the static state is what many users will spend most time seeing. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # CSS clamp(): Responsive Design Without Media Queries URL: https://theosoti.com/short/fluid-css-clamp/ Published: 2025-08-18 Tags: CSS, modern CSS, CSS layout, responsive design, frontend development, CSS tip > Learn how CSS clamp() creates fluid, responsive layouts by defining min, preferred, and max values—reducing media queries with 95% browser support. ## Media queries aren't the only solution for responsive design. With clamp(), you can reduce their use significantly! But wait, what is clamp()? Clamp is a native css function that allow you to specify: - a minimum value - a preferred value (usually with % or viewport units) - a maximum value Its syntax looks like this: clamp(5em, 30%, 10em) In short, it makes elements scale smoothly without relying on media queries. Clamp is often used for padding, margins, width and font-size. But it can handle anything with numerical values, such as: aspect-ratio, gap, positioning, … With ~95.2% browser support, it’s safe to use. A useful workflow is to start from two breakpoints you already trust, then derive the middle value so interpolation feels natural between them. This keeps fluid scaling intentional, not random. Clamp also pairs well with design tokens: expose min, ideal, and max values as variables so typography and spacing stay consistent across components while still adapting to viewport changes. `clamp()` is most reliable when min and max values come from clear design targets. Tune the middle value carefully, so scaling feels smooth across viewports instead of overreacting on very wide screens. `clamp()` performs best when min and max values are grounded in real design limits. Tuning the middle value carefully prevents exaggerated scaling on extreme viewport widths. Use this pattern to keep responsive behavior local to the component instead of scattering viewport overrides. That makes scaling and refactoring much safer. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # CSS @property: Animate and Validate Custom Variables URL: https://theosoti.com/short/new-css-variables/ Published: 2025-08-17 Tags: CSS, modern CSS, CSS animations, HTML forms, CSS tip > Learn how CSS @property upgrades custom variables with validation, debugging, and animation support. A must-have feature for cleaner, more powerful CSS. ## Unlock powerful animations with `@property` ! Defining all your CSS variables in :root? It works, but it has some limitations: - You can't tell if a variable is valid or not. - Impossible to animate certain values. - Debugging is messy. - Browsers give you zero feedback. That's where `@property` comes in. It doesn’t replace CSS variables, it upgrades them. With `@property`, you get automatic validation, better debugging, and the ability to animate certain types of variables that were never animatable before. If a value is invalid, it falls back cleanly instead of silently breaking your design. Plus, you get warnings when something’s wrong, no more guessing. If you want more powerful variables in your CSS, it’s time to make `@property` part of your toolbox. `@property` makes custom properties typed, animatable, and easier to validate. Defining syntax and initial values early prevents silent errors and enables smoother state transitions. `@property` gives custom properties explicit syntax, inheritance rules, and animatability. That makes advanced variable-driven UIs safer and easier to debug. Validate it with real content and realistic viewport ranges so implementation details hold up outside demos. When introducing `@property`, begin with one reference component and treat it as the canonical pattern. That rollout sequence preserves clarity and reduces regressions during future refactors. Before final rollout, validate `@property` against real content and real interaction states, not only demo text. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Native CSS Nesting vs Preprocessors, no more excuses URL: https://theosoti.com/short/native-css-nesting/ Published: 2025-08-16 Tags: CSS, modern CSS, CSS tip > CSS nesting is now natively supported with 91% browser coverage. Learn why you may not need Sass preprocessors anymore and how to switch smoothly. ## Stop using CSS preprocessor! CSS nesting is now natively supported. If you were using a preprocessor only for this feature, It might be time to go back to plain CSS. (Otherwise you can stick with it). 1/ No build tools required - It makes the development setup easier. - It also reduces the project’s build time. 2/ Reduced file size - Sass expands rules during compilation, increasing the final CSS size. - CSS nesting doesn't expand rules, resulting in smaller CSS. Browser support is already strong at 91.3%. But keep in mind that unsupported browsers will fail to parse nested rules. If you need full compatibility, consider using a build step until support is universal. If nesting was the only thing keeping you tied to a preprocessor… maybe it’s time to go vanilla. Native nesting improves readability when selector depth remains shallow. Keep nesting focused on component structure, so maintainability stays high and specificity does not creep upward. Native nesting improves readability when kept shallow and component-scoped. Avoid deep chains, so specificity and maintainability stay under control. Validate it with real content and realistic viewport ranges so implementation details hold up outside demos. When introducing `CSS nesting`, begin with one reference component and treat it as the canonical pattern. That rollout sequence preserves clarity and reduces regressions during future refactors. As a final check, run `CSS nesting` through edge cases like dense layouts, long labels, and constrained mobile widths. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Dynamic Layouts with CSS :has() without JavaScript URL: https://theosoti.com/short/switching-layout-in-CSS/ Published: 2025-08-15 Tags: CSS, modern CSS, CSS layout, responsive design, CSS selectors, performance, JavaScript alternatives > Learn how to build interactive layouts using CSS :has() and :checked. Create dynamic grids, galleries, and dashboards without JavaScript. ## Stop using Javascript to change your layout dynamically. You can do it entirely with CSS. Here’s how it works: - 1. Use input elements (checkbox or radio) to mimic click actions. - 2. The :has() pseudo-class lets a parent element respond to a child’s state. --> Example: `.container:has(input) .child {}` - 3. Use :checked to detect if an input is selected. --> Example: `input[type="radio"]:checked {}` - 4. Combine :has() and :checked to target specific states and apply styles dynamically. --> Example: `.container:has(input:checked) .child {}` In the example below, we just change the grid-template-columns values when a specific checkbox has been checked. You can use this for multiple things like e-commerce product, portfolio galleries, dashboards, blog layout, ... Modern CSS makes it easier than ever to create interactive layouts. Using `:has()` to switch layouts can remove a lot of small JS toggles. Keep the condition explicit and local, so layout changes happen only where users expect them. Using `:has()` for layout switching can remove small JS toggles while keeping logic declarative. Keep conditions explicit so layout changes remain predictable. This pattern has the best payoff when documented with one clear usage rule. Consistent adoption turns small CSS features into reliable system behavior. When introducing `.container:has(input) .child {}`, begin with one reference component and treat it as the canonical pattern. This keeps implementation predictable and prevents style drift as the codebase grows. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # CSS @scope: Encapsulated Styles Without CSS-in-JS URL: https://theosoti.com/short/scope-property/ Published: 2025-08-14 Tags: CSS, modern CSS, HTML, JavaScript alternatives, CSS tip > Discover how CSS @scope lets you limit style leakage, write cleaner HTML, and gain true encapsulation—no BEM, utility classes, or CSS-in-JS needed. ## Ever felt like your CSS is leaking all over the place? You’re not alone, it’s a familiar pain. But now, we have `@scope`. It lets you define where your styles begin, and where they end. Think of it like local styles, without the need for CSS-in-JS, BEM, or tons of utility classes. ```css @scope (.container) to (.post) { p { color: blue; } } ``` That `p` won’t be styled if it’s inside `.post`. Simple and clean. Here’s why this matters: - Styles don’t leak outside components - Fewer class names, cleaner HTML - Control over specificity and proximity - Donut scopes (yes, really) Even better, you can nest scopes. You can keep styles modular without naming gymnastics. This is real encapsulation, built into CSS itself. If you’ve ever wished CSS had better boundaries, this is it. The only downside is its browser support with only 86.56%. `@scope` helps contain styles close to their component boundary without extra wrapper classes. It is especially useful in content-rich pages where broad selectors might leak into unrelated sections. `@scope` helps isolate styles without additional wrappers. It is useful in content-heavy pages where broad selectors can accidentally leak into unrelated sections. Layout features like this provide real value when components move between narrow and wide containers. Testing nested contexts early prevents brittle breakpoint rules. When introducing `@scope`, begin with one reference component and treat it as the canonical pattern. This keeps implementation predictable and prevents style drift as the codebase grows. Document each scoped root clearly so future contributors can trace where styles start and where they are intentionally blocked. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # HTML inert: Disable Interactions and Improve Accessibility URL: https://theosoti.com/short/html-inert-attribute/ Published: 2025-08-13 Tags: HTML, accessibility, web development tip > Learn how the HTML inert attribute blocks interactions, hides content from screen readers, and improves accessibility for modals and pop-ups. ## Heard about the new HTML "inert" attribute? It’s a game-changer and it’s as simple as it is powerful. When you add inert to an element: - The element and everything inside it becomes inactive. - Users can’t click or interact with it. - Screen readers completely ignore it. It’s perfect for modals and pop-ups! With inert, you can ensure users stay focused on the modal without interacting with the rest of the page. It’s super handy for keeping users focused and making sure screen readers skip over stuff they don’t need to see. Bonus: better accessibility often means better SEO too! How does it work? Just add inert to any element you want to deactivate. While it’s there, users can’t click, tab, or interact with it. Remove it, and everything’s back to normal. It’s simple, effective, and takes no effort to set up. The best part ? It’s available on every major browser with a support of 94%! `inert` is a clean way to disable background interaction during modals and drawers. It blocks both pointer and focus access at once, which is safer than manually toggling many individual elements. `inert` is a clean way to disable background interaction during overlays. It blocks both pointer and focus access, reducing the risk of partially disabled UI states. Validate it with real content and realistic viewport ranges so implementation details hold up outside demos. When introducing `inert`, begin with one reference component and treat it as the canonical pattern. That rollout sequence preserves clarity and reduces regressions during future refactors. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Custom List Bullets in CSS with @counter-style URL: https://theosoti.com/short/customise-each-li-bullets/ Published: 2025-08-12 Tags: CSS, modern CSS, CSS tip > Discover how to customize each list bullet in CSS using @counter-style and list-style-type. Unique symbols, emojis, or characters in just a few lines. ## Ever wanted to customise each `
  • ` bullets individually? There is a way to do it in less than 5 lines of CSS! We only need 2 things to make it work: - The function “@counter-style” with some setup - The property “list-style-type” "list-style-type" is a pretty common property. It allows you to change the style of the bullet or to remove it completely. But here we will combine it with the @counter-style function. Here how it works: First, name your counter style (in this case, “cat-list”). Then add some key properties: - “symbols”: a list of elements (numbers, letters, emojis, special characters, etc.) that replace standard bullets. - “system”: defines the algorithm to convert a counter’s value into a character string. - “prefix”: adds characters before the symbol. - “suffix”: adds characters after the symbol. By default, this is a dot. The “system” property offers several options like “cyclic,” “numeric,” “alphabetic,” “symbolic,” “additive,” and “fixed.” Here, we chose “cyclic,” which means that if the list exceeds the number of symbols provided, it will loop back to the first one. Once everything is set up, link your custom style to “list-style-type" like so: “list-style-type: cat-list;". And voilà, you now have a list with unique, customised bullets. `@counter-style` is powerful for list systems where marker style communicates tone or structure. Keep marker contrast and spacing consistent for readability. Selector-driven improvements scale best when specificity remains controlled. Keep targeting rules readable so future edits do not become cascade puzzles. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # CSS Light Sweep Animation for Cards without JavaScript URL: https://theosoti.com/short/featured-card-animation/ Published: 2025-08-11 Tags: CSS, modern CSS, CSS animations, CSS gradients, performance, JavaScript alternatives, frontend development > Learn how to create a smooth light sweep effect on cards using only CSS gradients and keyframes. Simple, reusable, and no JavaScript required. ## Ever wondered how to do a light sweep animation? It’s pretty easy when you understand the logic. I’ll show you here how to do it for a featured card. But this can be declined for every components you need. And no JavaScript needed. Let's break it down into three key parts: 1- The card itself Start with a container. Add a child div for the animation. Style it however you like, but don’t forget `position: relative`. This anchors everything in place. 2- The animation Effect The rotating light effect uses CSS animations combined with a conic gradient. The animation layer fills the card with `position: absolute` and `inset: 0`. Use a custom CSS property to control the rotation value. It makes the animation flexible and easy to tweak. Next, define a keyframe to rotate the light beam 360 degrees. This will create that smooth sweep effect. 3- Superposing the :after element To make the card complete, we overlay an :after pseudo-element on the effect. It should sit on top with a slight inset (e.g., 4px) to create a border. This frames the animation and gives it a polished look. This approach is simple yet powerful. With just CSS, you can create a dynamic, visually engaging elements that grabs attention. Card light-sweep effects should be subtle and purposeful. They work well to indicate interactivity, but avoid constant motion that competes with core content. Always include reduced-motion behavior. A strong animation pattern is one that degrades cleanly while preserving meaning. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # CSS Safe Area Insets: Protect Content from Phone Notches URL: https://theosoti.com/short/safe-area-inset/ Published: 2025-08-10 Tags: CSS, modern CSS, responsive design, CSS layout, CSS shapes, frontend development, CSS tip > Learn how CSS safe area insets keep your UI visible on phones with notches, rounded corners, and gesture bars, with broad browser support. ## Stop your content from being cut off by phone notches. CSS safe area insets solve this problem easily. Today’s smartphones come with notches, rounded corners, and gesture bars. If you’re not accounting for them, your content risks being clipped or hidden. Enter CSS safe area insets with `env().` They keep your UI visible and out of those unsafe zones. With just a few lines: padding-bottom: 48px; padding-bottom: env(safe-area-inset-bottom); - Your sticky navbar or bottom buttons stay tap-friendly - Works automatically on devices with notches, rounded corners, or gesture bars - Falls back gracefully on devices that don’t need it The best part? This has a browser support of 96.78%! In production, apply safe-area values only where needed, usually fixed headers, sticky footers, and full-screen overlays. Avoid adding every inset everywhere, or spacing can feel oversized. A common pattern is combining a base padding with the environment value, for example `padding-bottom: max(16px, env(safe-area-inset-bottom));`. This preserves comfortable spacing on all devices. Safe area insets are most important for fixed UI near device edges. Combine baseline spacing with `env(safe-area-inset-*)` so controls stay comfortable on both notched and non-notched screens. Safe-area values matter most for fixed bars and edge-attached controls. Combine baseline spacing with `env()` insets to support both notched and non-notched devices. Before rollout, verify long content, dense cards, and extreme widths. Stable behavior under stress is what turns a neat trick into production CSS. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # New CSS Media Query Syntax: Easier, Cleaner, Faster URL: https://theosoti.com/short/new-media-queries/ Published: 2025-08-05 Tags: CSS, modern CSS, responsive design, CSS layout, CSS tip > CSS media queries now support range operators like ≤ and ≥. Write cleaner, more readable responsive rules with 90% browser support. ## Media queries aren't the only solution for responsive design. But the syntax can now be cleaner and easier to read. You can use range operators directly in media queries. That means `>=`, `<=`, `>`, and `<` logic in a natural order. Before this syntax, we often wrote long rules with `min-width` and `max-width`. They worked, but they were harder to scan. Now you can express the same condition in a shorter way. For example, `@media (width >= 48rem)` reads almost like plain language. You can also combine ranges in one condition. That makes breakpoint rules easier to reason about during reviews. The biggest gain is readability. When a file has many responsive conditions, simple syntax reduces mistakes. You spend less time decoding selectors and more time checking behavior. Range media queries are easier to read and reduce breakpoint logic mistakes. Standardize unit choice and naming conventions so responsive rules stay clear during team reviews. Range media queries improve readability and reduce breakpoint logic errors. Keep units and naming conventions consistent so responsive rules remain easy to review. This pattern has the best payoff when documented with one clear usage rule. Consistent adoption turns small CSS features into reliable system behavior. A practical way to adopt `min-width` is to scope it to one high-impact component first. Then reuse that same pattern in similar contexts so behavior stays consistent and review time stays low. Keeping breakpoint ranges non-overlapping makes cascade behavior easier to reason about and prevents hard-to-debug edge widths. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Create a Back to Top Button with Just HTML and CSS URL: https://theosoti.com/short/back-to-top/ Published: 2025-07-22 Tags: CSS, modern CSS, scroll-driven animations, CSS animations, accessibility, performance, HTML > Create a back to top button with HTML and CSS using native smooth scrolling. No JavaScript, lightweight, and accessible. ## Most “back to top” buttons use JavaScript. But they really don’t have to. The browser already knows how to scroll. We just have to ask nicely. All you need is: - Give your page an `id="top"` (usually on the ``). - Add a link with `href="#top"` that scrolls back to the top. - Use `position: fixed` to pin it in the corner. - And `scroll-behavior: smooth` on the html for smooth transitions No listeners. No scroll events. No extra code. This is simpler, faster, and works everywhere. Sometimes, native HTML and CSS are all you need. Checkout the codepen: https://codepen.io/theosoti/pen/KwdzKGx For accessibility, give the link a clear label (for example `aria-label="Back to top"`) and ensure the focus ring is visible when navigating by keyboard. Native anchors already preserve expected browser behavior, including opening in new tabs. If you use smooth scrolling, consider respecting `prefers-reduced-motion` by disabling animated scroll for users who opt out of motion. A native anchor-based back-to-top control stays lightweight and predictable across browsers. Place it where it is easy to reach, keep a clear label, and ensure anchor targets remain visible under sticky headers. Anchor-based back-to-top patterns stay lightweight and robust. Pair them with clear placement and heading offsets so users return to context without layout jumps. Validate it with real content and realistic viewport ranges so implementation details hold up outside demos. A practical way to adopt `position: fixed` is to scope it to one high-impact component first. After validation, expand gradually to matching components and avoid ad-hoc one-off overrides. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Style Native elements, no JS or libraries needed. ## Say goodbye to ugly `` elements have been frustrating to style. Often needing JavaScript or UI libraries. Now, modern browsers bring full customization to native dropdowns. What’s New? Chrome introduces new CSS properties for better `` styling 🔥 Checkout the codepen: https://codepen.io/theosoti/pen/VYwqEvP Custom-styled native selects should keep native behavior and clear affordances. Test focus rings, long option labels, and high-contrast states to ensure visual customization does not reduce usability. Styled native selects should retain clear affordances and keyboard behavior. Validate long options, focus rings, and contrast so visual customization does not harm usability. For production forms, test keyboard order, disabled states, and validation messages with realistic labels. Native behavior should remain intact while visual polish improves. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Truncate Multiline Text with Ellipsis Using Pure CSS URL: https://theosoti.com/short/truncate-multiline-text/ Published: 2025-07-18 Tags: CSS, modern CSS, CSS layout, responsive design, CSS typography, performance, CSS tip > Limit text to multiple lines with ellipsis using pure CSS. Perfect for cards, previews, and tight layouts. ## Have you ever had trouble creating multiple line breaks? Here is a cool CSS hack that cuts your text smartly. Simply add few lines of CSS to implement multiline truncation with ellipsis. Here’s what you need: ```css display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; ``` This CSS class will limit text content to three lines and hide the rest cut off content. To break it down: - display: -webkit-box = creates a flexible block container, similar to flexbox - webkit-line-clamp = limits the number of visible lines. - webkit-box-orient = ensures the content flows vertically. - overflow: hidden = keeps things clean by hiding the rest of the text. The result? Cut-off text that does not end colons. It’s ideal for cards, previews or anywhere space is tight. The best part? It has more than 96% browser support! Checkout the codepen: https://codepen.io/theosoti/pen/GgKJrwq Multi-line truncation is most useful in cards and previews where heights must stay aligned. Choose clamp values per breakpoint and provide a full-text view so users can access hidden content when needed. Line clamping is most useful for card grids where consistent heights matter. Pair truncation with accessible full-text paths so users can still access complete content. Test with multilingual strings and varied word lengths. Typographic CSS should remain graceful when content is unpredictable. A practical way to adopt `truncate multiline text` is to scope it to one high-impact component first. This keeps implementation predictable and prevents style drift as the codebase grows. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Crop and Zoom Images with CSS object-view-box URL: https://theosoti.com/short/shape-outside-image/ Published: 2025-07-16 Tags: CSS, modern CSS, CSS layout, responsive design, CSS shapes, CSS tip > Use object-view-box in CSS to crop and zoom images precisely—no image editing, just clean, flexible layout control. ## Crop images with precision, directly in CSS. Forget `clip-path` and forget editing assets. With `object-view-box`, you can zoom and crop right from your stylesheet. It's like `viewBox` in SVG, but for any element using `object-fit`. ```css img { object-fit: cover; object-view-box: inset(20% 0 0 0); } ``` The syntax is familiar: use `inset()` or even `rect()` to define your cropping zone. This works alongside `object-fit` to give you complete control over what portion of the image is displayed. Need to zoom on a specific part of an image? Want to highlight just a region inside a video or iframe? No JavaScript. No image editing. No container tricks. Just pure CSS. Browser support is still experimental (~76%), so don’t use it in production yet. But it’s available behind flags or in dev builds for testing. This property is the kind of thing you’ll soon wonder how you ever built layouts without. Checkout the codepen: https://codepen.io/theosoti/pen/yyYeENV Using `object-view-box` for focal cropping is useful when assets need consistent framing across different cards. Define focal rules early so important subject areas stay visible at every breakpoint. Focal cropping with `object-view-box` helps keep important image regions visible across reusable card formats. Set focal rules once, then verify results at multiple aspect ratios. Prefer subtle enhancement over heavy treatment. The visual should support hierarchy, not compete with content. To roll this out safely, start by applying `clip-path` in a single UI surface where the benefit is obvious. After validation, expand gradually to matching components and avoid ad-hoc one-off overrides. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Wrap Text Around Images with CSS shape-outside URL: https://theosoti.com/short/wrap-text-around-images/ Published: 2025-07-14 Tags: CSS, modern CSS, CSS layout, responsive design, CSS shapes, CSS typography, performance > Use shape-outside in CSS to wrap text around custom image shapes—no JavaScript, just clean, creative layout control. ## Make your text wrap around images perfectly. By default, text wraps around a boring rectangle. But what if you could make it hug the actual shape of the image? You can. With just one CSS property: `shape-outside: url(your-img.png);` Add a `float` and a bit of margin with `shape-margin`, and your layout feels instantly more refined. No JavaScript. No layout hacks. Just native CSS support and it's supported in over 95% of browsers. Use it with transparent PNGs, SVGs, or even basic shapes like `circle()` or `polygon()`. Ideal for editorial layouts, landing pages, or any design that needs more personality. Checkout the codepen: https://codepen.io/theosoti/pen/ogjjged `shape-outside` can produce elegant editorial layouts when image silhouettes stay simple. Test long paragraphs and varied image ratios, because float-based wrapping can become fragile in edge cases. `shape-outside` can create editorial layouts with strong flow, but test with varied image sizes and long text. Float-based wrapping can break faster than block layouts. Use this as a readability tool, not only a visual effect. The best result is when style improves scanning speed without adding cognitive load. To roll this out safely, start by applying `shape-outside: url(your-img.png);` in a single UI surface where the benefit is obvious. Then reuse that same pattern in similar contexts so behavior stays consistent and review time stays low. Before shipping, test `shape-outside: url(your-img.png);` with both short and long content, then verify behavior in narrow and wide containers. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Build an Autoplay Carousel with Pure CSS URL: https://theosoti.com/short/autoplay-carousel/ Published: 2025-07-12 Tags: CSS, modern CSS, CSS layout, responsive design, CSS animations, accessibility, performance > Create a smooth, accessible autoplay carousel using only CSS—no JavaScript, no libraries, and zero layout shifts. ## Still using JavaScript for an autoplay carousel? You don’t need to anymore. A one-directional, looping logo strip can now be built entirely with CSS. No `setInterval`. No libraries. No layout shifts. Here’s how it works: You define a few CSS custom properties: - `--width` and `--height` to size each item - `--quantity` to set how many elements are in the loop - `--duration` for how long one full cycle takes Each item gets a `--position`, so we can stagger its animation using `animation-delay`. This formula spaces each item evenly: ```css calc((var(--duration)/var(--quantity)) * (var(--position) - 1) - var(--duration)) ``` It ensures that the track starts full, no empty gaps at load. The animation itself is simple: From `left: 100%` to `left: -width`, sliding items across the track. We use `position: absolute` so every item animates independently, and `overflow: hidden` on the container to act as a viewport. For a smooth entrance and exit, we apply a `mask-image` gradient. This fades logos in/out without extra DOM or performance hits. Accessibility? Covered. Each logo gets `tabindex="0"` so keyboard users can focus them, and the entire animation pauses on hover or focus using `:has(:focus)` and `animation-play-state: paused`. No JavaScript. Just CSS, doing what used to take 100KB+ of JS. Checkout the codepen: https://codepen.io/theosoti/pen/OPVeegy Autoplay carousels should never hide control from users. Keep pause/stop behavior clear and predictable, and ensure content remains readable without waiting for automatic cycles. Motion should clarify state change, not distract from content. Keep timings short and ensure users can still complete actions quickly. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Create Accessible Popups with the Native HTML Popover API URL: https://theosoti.com/short/html-popover-api/ Published: 2025-07-10 Tags: HTML, accessibility, performance, JavaScript alternatives, frontend development, web development tip > Build tooltips, modals, and popups using the HTML Popover API—no JavaScript needed, fully accessible, and widely supported. ## Say hello to native popovers in HTML… Without a line of JavaScript! The Popover API lets you create smooth popups using just HTML and CSS. With the `popover` attribute, you can define elements that behave like tooltips, modals, or popups. Combine it with the `popovertarget` and `popovertargetaction` attributes, and you've got full control over when and how the popover opens or closes. Why use it? - Clean, semantic HTML structure. - No JavaScript needed - Full accessibility baked in (no extra ARIA attributes required). - Native browser support for interactions like open/close states. Bonus: It is supported by 90% of the browsers so far. So it is pretty safe to use! Checkout the codepen: https://codepen.io/theosoti/pen/ZENpowe The Popover API is ideal for lightweight overlays attached to a trigger context. Keep dismissal paths obvious with Escape and close controls, so keyboard and touch interactions feel equally reliable. The Popover API is ideal for lightweight overlays tied to a trigger context. Keep dismissal paths explicit with Escape and close controls for reliable keyboard and touch behavior. Check autofill, mobile keyboards, and error feedback before shipping. These practical states reveal more than static demos and keep the form experience trustworthy. A practical way to adopt `popover` is to scope it to one high-impact component first. This keeps implementation predictable and prevents style drift as the codebase grows. For critical actions, include a clear close button inside the popover so dismissal remains obvious even without keyboard input. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Style Native Form Elements with accent-color in CSS URL: https://theosoti.com/short/native-input-color/ Published: 2025-07-08 Tags: CSS, modern CSS, CSS colors, CSS typography, HTML forms, accessibility, performance > Easily customize checkboxes, radios, sliders, and more using accent-color—no wrappers, no JavaScript, fully accessible. ## Tired of blue checkboxes that don’t match your brand? Now you can fix that in one line of CSS. Native form elements are finally catching up. You can now change the color of checkboxes, radios, range sliders, and even progress bars. Without JavaScript, wrappers, or hacks. It’s as simple as: ```css input, progress { accent-color: red; } ``` Why this matters: - Matches your brand identity down to the smallest details - Works on all common input types, no need for custom components - Keeps the native behavior and accessibility intact It’s supported in all major browsers (even mobile!) with 94,6%. So if you’re still using SVG icons or replacing inputs with divs just to match a color, you can stop now. `accent-color` gives quick brand alignment for native controls with almost no overhead. Treat it as a polish layer and still verify contrast, focus visibility, and disabled states across form components. `accent-color` is a fast way to align native controls with brand tone while keeping semantics intact. Treat it as polish, and still verify contrast and focus clarity across states. On form-heavy pages, this is most effective when you keep semantics native and style enhancements lightweight. That balance preserves accessibility and reduces maintenance cost. When introducing `accent-color`, begin with one reference component and treat it as the canonical pattern. That rollout sequence preserves clarity and reduces regressions during future refactors. As a final check, run `accent-color` through edge cases like dense layouts, long labels, and constrained mobile widths. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Unlock All 4 CSS Focus States and 1 Hidden Gem URL: https://theosoti.com/short/4-css-focus-states/ Published: 2025-07-07 Tags: CSS, modern CSS, accessibility, CSS tip > Learn the 4 focus states in CSS. :focus, :focus-visible, :focus-within, and a hidden gem—for better accessibility and smoother UX. ## Did you know there are 4 different focus states in CSS? One of them is secret 🤫 CSS Focus states might seem straightforward at first. In reality, there are multiple selectors that control how and when focus is displayed on elements. There are three primary focus states in CSS, plus a “secret” fourth one. Here are the 3 main CSS focus: - **:focus** the most common one - **:focus-visible**, it’s like :focus, but visible only on certain conditions - **:focus-within** will focus the container element when one of its children is focused For the fourth one, you can go check my blog article here: https://theosoti.com/blog/css-focus-hack/ Understanding how to use these focus states correctly can elevate accessibility, create smoother interfaces, and improve user navigation. Separating focus states by intent improves both accessibility and visual clarity. Using `:focus`, `:focus-visible`, and container states deliberately prevents noisy outlines while preserving strong keyboard feedback. Using focus states deliberately (`:focus`, `:focus-visible`, and container variants) improves keyboard UX and avoids noisy outlines for mouse users. Validate it with real content and realistic viewport ranges so implementation details hold up outside demos. A practical way to adopt `:focus` is to scope it to one high-impact component first. After validation, expand gradually to matching components and avoid ad-hoc one-off overrides. Before final rollout, validate `:focus` against real content and real interaction states, not only demo text. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Simplify CSS Animations with Individual Transform Properties URL: https://theosoti.com/short/individual-transform-properties/ Published: 2025-07-06 Tags: CSS, modern CSS, CSS animations, HTML forms, CSS tip > Use translate, rotate, and scale as separate CSS properties for cleaner, modular, and easier-to-maintain animations. ## Animations just got easier to manage. And your CSS just got a whole lot cleaner. For years, combining multiple values in transform meant repeating the full list every time you wanted to change just one. It worked, but it wasn’t ideal. Now you can split transforms into individual properties: - translate - rotate - scale No more repetition. No more overrides. Just clean, focused changes. Why it's better: - Each transformation is isolated. You can update one without touching the others. - It makes animations more readable, modular, and easier to maintain. - You can now animate them independently at different speeds or stages. Browser support is solid across all modern browsers. This is one of those tiny syntax upgrades that makes a big difference. Individual transform properties make motion easier to tune because each axis can be adjusted independently. This keeps animation edits cleaner than repeatedly rewriting long `transform` value chains. Individual transform properties make motion tuning cleaner because each axis can be adjusted independently. This avoids repeatedly rewriting long combined transform values. For production forms, test keyboard order, disabled states, and validation messages with realistic labels. Native behavior should remain intact while visual polish improves. A practical way to adopt `transform` is to scope it to one high-impact component first. Then reuse that same pattern in similar contexts so behavior stays consistent and review time stays low. This is especially helpful during iterative motion tuning because each property can be adjusted without rewriting the whole transform chain. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Build Smarter Layouts with CSS Container Queries URL: https://theosoti.com/short/container-queries/ Published: 2025-07-05 Tags: CSS, modern CSS, container queries, responsive design, CSS layout, frontend development, CSS tip > Make components truly responsive with CSS container queries. Style elements based on parent size, not screen width. ## Responsive design isn’t just about screen size anymore. Container queries give components the power to adapt on their own. Responsive design isn’t just about screen size anymore. Container queries give components the power to adapt on their own. That’s where CSS container queries come in. They let you apply styles based on the size of the parent, not the window. What makes them special: - Components become truly reusable and context-aware. - You can build layouts that behave consistently in any environment. - No more overrides based on where a component happens to land. How it works: - Set a container with `container-type: inline-size;`. - Inside that container, use `@container` rules to style elements based on width. Browser support is already strong at ~94%. Just keep in mind: unsupported browsers will skip `@container` rules entirely. So be sure your base styles hold up without them. Don't hesitate to read the full article on my blog: https://theosoti.com/blog/container-queries/ Container queries shine when the same component appears in narrow and wide contexts. Defining behavior from container size, not viewport size, keeps modules reusable without layout-specific overrides. Container-driven rules make components genuinely reusable across unknown parent layouts. This reduces breakpoint duplication and keeps local behavior tied to actual available space. Use this pattern to keep responsive behavior local to the component instead of scattering viewport overrides. That makes scaling and refactoring much safer. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Add Smooth Page Transitions with CSS @view-transition URL: https://theosoti.com/short/view-transition/ Published: 2025-07-04 Tags: CSS, modern CSS, CSS animations, performance, JavaScript alternatives, CSS tip > Use @view-transition to create crossfade effects between pages—no JavaScript, just clean, progressive CSS. ## Page loads don’t have to be harsh. CSS lets you add smooth transitions between pages with 1 line. For a long time, animating between two pages required JavaScript tricks, frameworks, or SPA logic. Now? It’s built into the browser. By adding this tiny CSS snippet, you can crossfade between pages automatically: ```css @view-transition { navigation: auto; } ``` What it does: It tells the browser to animate between navigations, using a smooth crossfade. Why it’s so cool: - It works instantly, no JS needed - If the browser doesn’t support it, nothing breaks - It makes your site feel way more polished This is progressive enhancement at its best. Old browsers ignore it (81% support currently). New ones reward you with smooth UX. And that’s just the default behaviour. You can customise it to animate specific elements, trigger transitions manually, and more. `@view-transition` can make page changes feel continuous instead of abrupt. Keep transitions short and meaningful, and ensure unsupported browsers still get stable navigation without visual glitches. View transitions are best when they explain navigation changes, not when they add spectacle. Keep timings short and fallbacks clean for unsupported browsers. Run interaction tests with rapid clicks and navigation changes. Input should stay responsive even while transitions are active. For teams, `@view-transition` is easiest to maintain when it starts in one documented example before broader reuse. That rollout sequence preserves clarity and reduces regressions during future refactors. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Smooth Scrolling with CSS scroll-behavior URL: https://theosoti.com/short/smooth-scroll/ Published: 2025-07-03 Tags: CSS, modern CSS, scroll-driven animations, CSS animations, performance, JavaScript alternatives, frontend development > Enhance anchor navigation with smooth scrolling and better section spacing using pure CSS—no JavaScript required. ## Jumping straight to a section feels rough. Smooth scrolling makes navigation feel effortless. When users click an anchor link, the browser jumps abruptly to the section. This can be jarring, especially on long pages. Instead of relying on JavaScript for smooth scrolling, CSS has a built-in solution: “scroll-behavior: smooth”. With just this, your page transitions fluidly between sections. But what if the content feels too close to the edge? By default, scrolling stops at the very top of the section, sometimes cutting off important content. Fix this with `scroll-padding` or `scroll-margin`: scroll-margin-top: 20px; Now, every scroll lands in a comfortable, readable position. It’s lightweight, easy to implement, and widely supported (more than 95,5%). Here's the codepen: https://codepen.io/theosoti/pen/KKOGJjZ Native smooth scrolling works well for in-page links and table-of-contents navigation. Pair it with heading `scroll-margin` so anchors land with comfortable spacing below fixed interface chrome. Native smooth scrolling is great for in-page navigation when motion remains subtle. Add heading offsets so anchor jumps feel intentional, not cramped under fixed UI chrome. Run interaction tests with rapid clicks and navigation changes. Input should stay responsive even while transitions are active. A practical way to adopt `scroll-padding` is to scope it to one high-impact component first. This keeps implementation predictable and prevents style drift as the codebase grows. When using sticky headers, set heading-specific `scroll-margin-top` values so each section lands with consistent breathing room. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Create Animated Gradient Borders with Pure CSS URL: https://theosoti.com/short/border-gradient/ Published: 2025-07-02 Tags: CSS, modern CSS, responsive design, CSS layout, CSS gradients, performance, JavaScript alternatives > Build clean, animated gradient borders using only CSS—no extra elements, fully responsive, and JavaScript-free. ## You can create gradient borders in pure CSS. No extra elements. No pseudo-elements. Here’s how it works: Set a thick transparent border. Then layer two backgrounds: - A solid fill using `padding-box` - A gradient using `border-box` The transparent border acts as space for the gradient to show through. It works seamlessly with `border-radius`. And because it's just one element, it keeps your HTML clean. Now let’s go a step further: animation. With the `@property` rule, you can register custom values, like an angle. That allows CSS to transition between them smoothly. Define something like `--angle` for your gradient direction. Update it on hover or with a keyframe. CSS will animate the change automatically, no JavaScript needed. This opens up subtle, lightweight effects for buttons, cards, and more. And since it's based on native CSS features, it's well-supported and performant. Here's the codepen: https://codepen.io/theosoti/pen/myJzeKN Animated gradient borders are visually strong, so moderation matters. They work best on featured surfaces or key CTAs, while everyday components should keep calmer borders for better hierarchy. Animated gradient borders work best as highlights, not defaults. Use them on featured components and keep everyday surfaces visually calmer to preserve hierarchy. Always include reduced-motion behavior. A strong animation pattern is one that degrades cleanly while preserving meaning. A practical way to adopt `padding-box` is to scope it to one high-impact component first. After validation, expand gradually to matching components and avoid ad-hoc one-off overrides. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Style Drop Caps in CSS with :first-letter and initial-letter URL: https://theosoti.com/short/customise-first-letter/ Published: 2025-07-01 Tags: CSS, modern CSS, CSS typography, CSS tip > Add editorial flair to paragraphs using :first-letter and initial-letter in CSS—no extra markup, just clean, elegant styling. ## Want to give your paragraphs a touch of editorial flair? CSS lets you style and position the first letter without any extra markup. With the :first-letter pseudo-element, you can create drop caps and elegant intros like those seen in magazines. The real magic comes from the initial-letter property. It controls both the size and alignment of the first letter in just one line. For example: `initial-letter: 3 2;` means the letter spans 3 lines and aligns against 2 lines of baseline text. You can then style it as you want. Browser support is solid at over 95%. Have you used :first-letter creatively before? Would love to see it! Here is a live example: https://codepen.io/theosoti/pen/PwoaJNP Drop-cap styling is strongest when it supports editorial tone rather than decoration for its own sake. Keep spacing and baseline alignment controlled, especially on small screens where oversized initials can disrupt flow. Drop-cap styling with `:first-letter` should support reading tone, not dominate it. Tune spacing carefully so the opening paragraph remains comfortable to scan. Typography refinements are most visible with real content length and mixed wording. Validate rhythm, spacing, and line breaks on mobile where small issues become obvious. For teams, `initial-letter: 3 2;` is easiest to maintain when it starts in one documented example before broader reuse. After validation, expand gradually to matching components and avoid ad-hoc one-off overrides. On narrow screens, reduce the drop-cap size slightly to keep the first lines readable and avoid awkward text collisions. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Smarter Layouts with CSS calc-size() without JS URL: https://theosoti.com/short/ratings-calc-size/ Published: 2025-06-25 Tags: CSS, modern CSS, CSS layout, responsive design, JavaScript alternatives, frontend development, CSS tip > Use the new calc-size() CSS function to size elements dynamically from layout and attribute values—no JS, just responsive, flexible UI. ## Smarter sizing with just one CSS function. Let your content define the layout... dynamically. You can now use calc-size() to compute size values based on both layout constraints and live attributes. Why is that a big deal? Because it makes things like this rating system possible with: - No JS - No inline styles - No custom properties Just pure CSS, reacting to attributes like data-rating. The calc-size() function is a new addition to CSS. It allows you to perform calculations using intrinsic size values like auto, min-content, max-content, or fit-content. These values aren’t supported in regular calc(), which makes calc-size() a powerful new tool. You can also use this function to animate an element’s height to auto! It’s powerful, it’s expressive, and it unlocks a new level of flexibility for dynamic UI design. Here's the codepen link: https://codepen.io/theosoti/pen/gbOVBVp `calc-size()` is useful when component dimensions should adapt without JS measurement loops. It works well for controls and rating UIs where content-driven sizing still needs predictable limits. `calc-size()` helps combine content-driven sizing with fixed constraints. It is useful when UI parts should flex naturally but still remain within intentional layout bounds. This pattern has the best payoff when documented with one clear usage rule. Consistent adoption turns small CSS features into reliable system behavior. When introducing `calc-size()`, begin with one reference component and treat it as the canonical pattern. This keeps implementation predictable and prevents style drift as the codebase grows. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Create Animated Text Gradients with Pure CSS URL: https://theosoti.com/short/animated-text-gradient/ Published: 2025-06-20 Tags: CSS, modern CSS, CSS animations, CSS gradients, CSS typography, performance, JavaScript alternatives > Use background-clip: text and keyframes to animate gradients on text—no JavaScript needed, fully CSS-powered, and smooth across modern browsers. ## Animated text gradients with pure CSS? Yes, it’s possible, and surprisingly smooth. This technique uses `background-clip: text` to apply a gradient directly to the text. It then animates the background to create a subtle shimmering effect. A few things that make it work: - Set the text color to `transparent` so only the background shows through. - Use `background-size: 200% auto` to give the gradient space to move. - Animate the `background-position` with keyframes to create motion. - Repeat the first color at the end of your gradient (color1 → color2 → color1) to make the loop seamless. No JavaScript needed. Just clever layering with modern CSS. Support is solid across Chromium and WebKit browsers, with Firefox catching up. Here's the codepen: https://codepen.io/theosoti/pen/ogNoRPp Animated text gradients are most effective when motion stays restrained and contrast remains high. The static state should still read clearly, so users with reduced motion preferences keep full readability. Animated text gradients should be treated as accent motion, not core content delivery. Keep fallback readability strong so users still understand the message without animation. Use this as a readability tool, not only a visual effect. The best result is when style improves scanning speed without adding cognitive load. A practical way to adopt `background-clip: text` is to scope it to one high-impact component first. After validation, expand gradually to matching components and avoid ad-hoc one-off overrides. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Trim text spacing with text-box-trim and text-box-edge URL: https://theosoti.com/short/text-box-trim/ Published: 2025-06-19 Tags: CSS, modern CSS, CSS layout, responsive design, CSS typography, frontend development, CSS tip > Use text-box-trim and text-box-edge to remove excess space around text for cleaner UI layouts—perfect for buttons, labels, and tight spacing. ## Tired of inconsistent text spacing in your UI? CSS has a new fix: `text-box-trim` and `text-box-edge`. These properties let you trim excess space around text for tighter, more predictable layouts. Here’s the syntax: text-box-trim: trim-start | trim-end | trim-both; text-box-edge: cap | ex | alphabetic | text; What they do: - `text-box-trim`: controls which edge(s) of the text box to trim. - `text-box-edge`: defines what the top and bottom of the box align to. Key values: - `cap`: aligns to the cap height of the font (great for uppercase headers). - `alphabetic`: aligns with the text’s baseline (standard for most Latin scripts). - `ex`: based on the height of a lowercase “x”, useful for lowercase-heavy text. - `text`: uses the full visual bounds of the glyphs (like accents or descenders). Pairing `trim-both` with `cap alphabetic` gives you ultra-clean vertical alignment. Perfect for buttons, labels, and tight UI layouts. Browser support is still limited with ~80%. But unsupported browsers will ignore them and fall back to the default text box behaviour. Here's the codepen: https://codepen.io/theosoti/pen/azORoJZ `text-box-trim` is valuable when vertical spacing must feel optically balanced, not only mathematically centered. It is especially useful for buttons, chips, and labels where tiny spacing inconsistencies are very visible. `text-box-trim` helps when optical alignment matters, especially for compact controls like chips, pills, and buttons where tiny baseline gaps are noticeable. Typography refinements are most visible with real content length and mixed wording. Validate rhythm, spacing, and line breaks on mobile where small issues become obvious. --- If you liked this tip, you might enjoy the book, which is packed with similar insights to help you build better websites without relying on JavaScript. Go check it out https://theosoti.com/you-dont-need-js/ and enjoy 20% OFF for a limited time! --- # Enhance Form UX with the Native Element URL: https://theosoti.com/short/form-datalist/ Published: 2025-06-18 Tags: HTML forms, accessibility, performance, web development tip > Improve form usability using for autocomplete suggestions—lightweight, accessible, flexible, and fully supported in modern browsers. ## Build better forms with one HTML tag Autocomplete. Suggestions. Flexibility. The native `` element can help improve form UX. Here’s how it works: You pair a standard `` with a `` using the `list` attribute. Each `