Technote

Themes & plugins Advanced

Interactions without JavaScript, and the rule that makes them safe

details, :target, :focus-within, :has() and scroll timelines cover a surprising amount of interaction. One rule keeps them safe: the default state must be the finished state.

Accordions, tabs, elements that appear as you scroll — all of it used to be script work. A good deal of it now finishes inside the stylesheet. Less script means fewer requests and less execution time, and above all the page does not die when a script fails.

The newer the feature, though, the patchier the browser support. So there is a rule to settle before any of the techniques.

The rule: the default state must be the finished state

That single line is what makes CSS interaction safe. It means a browser that supports none of these features must receive a complete static screen, not an empty space.

What matters is what happens when you get it backwards. Hide an element at opacity: 0 and let a scroll animation raise it to one, and in a browser without that animation the content never appears at all. The page returns 200, the markup is intact, and a human sees a blank band — the sort of fault that is discovered last.

The same effect; where the default sits decides what failure looks like

In practice that means keeping the animation declarations inside a support query and a reduced-motion query. Every scroll effect on this site is built that way — open this page in a browser without support and you lose the movement, not the words.

Five things you can genuinely use

1. <details> for accordions and FAQs. Opening and closing is native, so keyboard operation and assistive support arrive for free. It also matters that the content stays in the document for search and printing; script-built accordions sometimes remove closed content from the DOM entirely, and what is not there cannot be found.

2. :target for state in the address. Rules apply to whatever the link points at, which is enough for footnote highlighting, simple tabs and modal-like panels. State living in the URL is both the strength and the limit — the link shares that exact state, but the back button now walks through it.

3. :focus-within so a parent knows about focus inside it. Highlight the whole search box when the cursor enters the field, for instance. The real value is that keyboard users get the same feedback mouse users do.

4. :has() to lift a condition to the parent. “Lay out cards without an image differently”, “show the toolbar when something in the list is ticked” — rules that used to require the server or a script to add a class now finish in CSS.

5. Scroll-driven animation. Progress bars, entrance fades, a rail that draws itself alongside a procedure, all without a scroll handler. Removing the pattern where script reads positions on every scroll makes the scrolling itself lighter.

/* Default = final state. This is what every browser receives */
.reveal { opacity: 1; translate: 0 0; }

/* Motion only where it is supported and wanted */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .reveal {
      animation: reveal-in linear both;
      animation-timeline: view();
      animation-range: entry 10% cover 35%;
    }
  }
}

@keyframes reveal-in {
  from { opacity: 0; translate: 0 16px; }
  to   { opacity: 1; translate: 0 0; }
}

There is a reason that example animates the translate property rather than transform. A filled animation on transform overrides a card’s hover transform, so only the cards that have been revealed lose their hover. Because the two properties are separate, one can belong to the animation and the other to the hover without interference.

Where to stop

Pushing everything into CSS eventually makes things worse. The boundary sits at the accessibility contract.

What CSS finishes and what it does not — the last three owe a contract

The hidden checkbox trick deserves particular care. What people see is a tab or a toggle; what exists is a checkbox, and that is what a screen reader announces. It looks perfect and only the semantics are wrong, which is precisely the kind of fault a visual review will never find.

The test is simple: when what is seen and what is announced start to diverge, that is where you stop. Up to that line CSS is sturdier than script; past it you need a properly built one.

Working through focus, screen readers and keyboard operation in order is covered in the accessibility series, and the cost of script weight lives in the performance archive. If you would like to find out what could be removed from a live site, see our optimization program.

More on this topic

All technotes

Themes & plugins Practical

Decide the editable regions before you design them

A design the CMS cannot express stays up for negotiation long after it ships. Deciding what editors may change, first, removes the negotiation entirely.

Designers 6 min read

Themes & plugins Practical

Icon fonts, SVG sprites or inline SVG?

All three draw the same picture on screen. What differs is how many colours you can use, what assistive technology hears, and how many icons actually ship to…

Designers 10 min read

₩270,000 · Join the program