Quick practical upgrade pack for truly declarative, JS‑free UI logic using modern CSS: :target, :has(), and container style queries.
Use hash links (#id) to open/close sections, focus cards, or reveal details. It’s native, accessible, and back/forward‑button friendly.
<article id="details" hidden>
<h2>Details</h2>
<p>...</p>
<p><a href="#close">Close</a></p>
</article>
<nav>
<a href="#details">Open details</a>
<a id="close"></a> <!-- empty anchor to "unset" target -->
</nav>#details { display: none; }
:target#details { display: block; }Tips
- Use an empty “close” anchor to clear
:target. - Add
scroll-margin-blockon targets to avoid header overlap.
Make a container react to what it contains—checked inputs, non‑empty slots, validation, etc.
<section class="card">
<header>
<label>
<input type="checkbox" hidden>
Toggle
</label>
<h3>Card</h3>
</header>
<div class="panel">Content…</div>
</section>.card .panel { display: none; }
.card:has(> header input:checked) .panel { display: block; }
/* Validation example */
.form-row:has(input:user-invalid) { outline: 2px solid color-mix(in oklch, red, Canvas 40%); }Tips
- Prefer structural selectors (
>,:empty, native attributes) over classes to fit D7460N’s CSS‑first rules. - Combine with
[open],[aria-expanded="true"], or:modalfor native states.
Style queries let components adapt based on a parent’s computed styles (tokens), not brittle utility classes. Great for theming, modes, or capability flags.
<div class="shell" style="--mode: 'compact'">
<ul class="list">
<li>…</li>
</ul>
</div>/* declare the container */
.shell { container-type: inline-size; container-name: shell; }
/* structural size query (classic container query) */
@container shell (min-width: 42rem) {
.list { columns: 2; gap: 1rem; }
}
/* style query: react to a *value* instead of a class */
@container style(--mode: 'compact') {
.list { gap: 0.5rem; font-size: 0.95em; }
}Tips
- Use one semantic custom property (e.g.,
--mode,--intent,--variant) at the shell level. - Components inside read the parent’s “state” via
@container style(...).
<main class="app" style="--mode: 'cozy'">
<section id="panel" class="card">
<header>
<h2>Panel</h2>
<label>
<input type="checkbox" hidden>
Expand
</label>
<a href="#panel">Focus</a>
<a href="#_">Unfocus</a>
</header>
<div class="body">…content…</div>
</section>
</main>/* containers */
.app { container-type: inline-size; container-name: app; }
.card { container-type: inline-size; container-name: card; }
/* :target focuses the card */
.card { outline: none; }
:target.card { outline: 2px solid var(--accent, Highlight); outline-offset: 4px; }
/* :has() opens body */
.card .body { display: none; }
.card:has(> header input:checked) .body { display: block; }
/* size-based layout */
@container app (min-width: 56rem) {
.card { display: grid; grid-template-columns: 18rem 1fr; gap: 1rem; }
}
/* style query for mode */
@container style(--mode: 'cozy') {
.card { padding: 1rem; border-radius: 0.75rem; }
}
/* accessible motion preference */
@media (prefers-reduced-motion: no-preference) {
.card .body { transition: content-visibility 0s, opacity .2s, scale .2s; }
.card:has(> header input:checked) .body { opacity: 1; scale: 1; }
.card .body { opacity: .001; scale: .98; }
}:target— deep-linkable show/hide, focus, or “open to a section”.:has()— parent reacts to child state (checked, valid, non‑empty).@container style()— components adapt to parent “mode” tokens without classes.
