Field notes
Published August 12
A short piece of standalone content. The surface and border keep it separate from the prose around it.
Docs
Articles, details, navigation, dialogs, and popovers.
Most components are just elements. EldanCSS adds structure without requiring a wrapper class.
article gets a surface, a border, and padding. Use its header and footer for supporting metadata.
<article>
<header>
<h3>Field notes</h3>
<p>Published August 12</p>
</header>
<p>A short piece of standalone content.</p>
<footer>
<small>3 minute read</small>
</footer>
</article>
Published August 12
A short piece of standalone content. The surface and border keep it separate from the prose around it.
details and summary provide disclosure without JavaScript. The summary row is the control; the plus and minus marker shows the current state.
<details>
<summary>What is a classless stylesheet?</summary>
<p>A stylesheet that targets semantic elements instead of requiring utility classes.</p>
</details>
A stylesheet that targets semantic elements instead of requiring utility classes.
No. The stylesheet is CSS only. This documentation uses a small script for the interactive examples.
<figure>
<img src="/image.jpg" alt="Description">
<figcaption>An optional caption.</figcaption>
</figure>
Images and SVGs are fluid. Figures remove the default inline margin, and captions use the muted text color.
<progress value="68" max="100">68%</progress>
<meter value="0.62">62%</meter>
Both controls use the accent color and fill the available width.
A nav containing a list becomes a horizontal menu. Give each nav an accessible name, and mark the current item with aria-current="page".
<nav aria-label="Primary">
<ul>
<li><a href="/" aria-current="page">Home</a></li>
<li><a href="/docs/">Docs</a></li>
<li><a href="/demo/">Demo</a></li>
</ul>
</nav>
Nested lists switch to a vertical layout for simple submenus. A full sidebar can override the top-level list with display: grid or display: block in your own CSS.
Set aria-busy="true" on the region that is working. The stylesheet changes the cursor and appends an animated ellipsis that uses the current text color and keeps the label in place.
<button type="button" aria-busy="true">Saving</button>
<p aria-busy="true">Fetching entries</p>
Fetching entries
A dialog is either modal or non-modal. Modal dialogs make the rest of the page inert and trap focus; non-modal dialogs leave the page interactive. The stylesheet styles both.
| Pattern | Modal | JavaScript |
|---|---|---|
<dialog> + showModal() | Yes | Yes |
<dialog popover> + popovertarget | No | No |
<dialog> + command="show-modal" | Yes | No (Baseline 2025) |
<dialog aria-labelledby="dialog-title">
<h2 id="dialog-title">Dialog title</h2>
<p>Native dialog content.</p>
<form method="dialog">
<button>Close</button>
</form>
</dialog>
showModal() opens the dialog modally: focus moves inside, the rest of the page becomes inert, and Esc closes it. The demo below uses a tiny script to call it.
For a JS-free modal, the Invoker Commands API offers a declarative equivalent:
<button type="button" commandfor="dialog-id" command="show-modal">Open dialog</button>
<dialog id="dialog-id" aria-labelledby="dialog-title">
<h2 id="dialog-title">Dialog title</h2>
<p>Native modal dialog content.</p>
<button type="button" commandfor="dialog-id" command="close">Close</button>
</dialog>
commandfor and command are Baseline 2025 newly available. Browsers without them will not open the dialog, so the script above remains the portable path.
Add popover to a dialog and open it with popovertarget. The browser handles opening, light dismissal, and the top layer without a script. The dialog keeps dialog semantics but is non-modal: the page stays interactive, focus is not trapped, and the backdrop is transparent by default.
<button type="button" popovertarget="dialog-id">Open dialog</button>
<dialog popover id="dialog-id" aria-labelledby="dialog-title">
<h2 id="dialog-title">Dialog title</h2>
<p>Non-modal dialog content.</p>
<button type="button" popovertarget="dialog-id" popovertargetaction="hide">Close</button>
</dialog>
Popovers share the dialog surface but intentionally keep ::backdrop transparent, so menus and other popovers do not dim the page. To give a specific non-modal dialog a modal-like backdrop, style its ::backdrop:
#dialog-id::backdrop {
background: hsl(var(--hue) calc(20% * var(--saturation)) 5% / 0.65);
}
The same hook can blur the page with backdrop-filter instead of dimming it. A dimmed backdrop does not make the dialog modal; modality comes from showModal() or command="show-modal", not from the backdrop. The demo above opts in with docs-only CSS; EldanCSS does not apply a backdrop to every popover.
A popover is a native, non-modal surface. Unlike a modal dialog, it does not trap focus or make the rest of the page inert. Add popover to an element and point a button at it with popovertarget. The browser opens it in the top layer and closes it on outside click or Esc; no script is required.
<button type="button" popovertarget="menu">Menu</button>
<nav popover id="menu" aria-label="Menu">
<ul>
<li><a href="/docs/">Docs</a></li>
<li><a href="/demo/">Demo</a></li>
</ul>
</nav>
Popovers share the dialog surface. A nav inside a popover becomes a vertical menu, so the same semantic markup works as a small navigation panel. Unlike modal dialogs, popovers have a transparent ::backdrop by default; see the non-modal dialog example above for an opt-in dimmed backdrop.
Popovers are centered by default. For a menu anchored to its trigger, add CSS anchor positioning in your own styles; the stylesheet does not depend on it.