HTML Data Attributes, Highlight, and Media Elements
Three HTML features people forget about: `data-*` attributes for stashing state and config on elements (queryable from CSS and JS), the semantic-highlight elements (`<mark>`, `<dfn>`, `<kbd>`, `<samp>`, `<var>`) that newcomers reach for `<span>` instead, and the HTML5 media elements (`<video>`, `<audio>`, `<source>`, `<track>`) with their key attributes. Treat this as a reference; you do not need to memorize it.
355 views
5
<!-- data-* attributes attach arbitrary key-value pairs to any element.
They are valid HTML, queryable from CSS via [data-foo="..."], and exposed
to JavaScript on the element's .dataset property as a kebab-to-camel map. -->
<button id="toggle" data-state="closed" data-target="#menu">
Toggle menu
</button>
<nav id="menu" data-state="closed">
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<!-- CSS can react to data-state changes without any class swapping. -->
<style>
[data-state="open"] {
display: block;
}
[data-state="closed"] {
display: none;
}
</style>
<!-- JavaScript reads and writes via element.dataset. The HTML attribute
data-user-id becomes element.dataset.userId in JS (kebab -> camel). -->
<script>
const btn = document.getElementById('toggle');
const menu = document.getElementById('menu');
btn.addEventListener('click', () => {
const next = menu.dataset.state === 'open' ? 'closed' : 'open';
btn.dataset.state = next;
menu.dataset.state = next;
});
</script>data-* attributes are the standard place to put per-element state and configuration: a card's data-card-id, a tab's data-active, a button's data-target. They are valid HTML, do not collide with future spec additions, and round-trip cleanly between CSS attribute selectors and the JavaScript element.dataset API. The naming convention is kebab-case in HTML (data-user-id) and camelCase in JS (element.dataset.userId). Use them for state machines and small config; do not abuse them for large blobs of JSON, since attributes are strings and parsing on every read is wasteful (use a <script type="application/json"> tag for that).
<!-- <mark>: highlighted or relevant text. Browsers render with a yellow
background by default. Useful for search-result highlighting. -->
<p>The fastest sorting algorithm in the average case is <mark>quicksort</mark>.</p>
<!-- <dfn>: the term being defined within its surrounding context.
The first occurrence of a definition should use this. -->
<p><dfn>Currying</dfn> is the technique of converting a function that takes
multiple arguments into a sequence of single-argument functions.</p>
<!-- <kbd>: keyboard input the user should press. Often styled as a key cap. -->
<p>Press <kbd>Cmd</kbd> + <kbd>K</kbd> to open the command palette.</p>
<!-- <samp>: sample output from a program or system. Monospace by default. -->
<p>The script printed <samp>Compilation complete: 0 errors, 3 warnings.</samp></p>
<!-- <var>: a variable in a mathematical or programming context. Italic by
default and distinct from <i> because it carries semantic meaning. -->
<p>The volume of a sphere is <var>V</var> = (4/3) * pi * <var>r</var><sup>3</sup>.</p>
<!-- These five render with sensible default styles in every browser, but
the real value is semantic: screen readers and search engines understand
what each one means. Reach for these BEFORE wrapping text in a generic span. -->These five elements are the semantic equivalents of <span> for specific kinds of inline content. <mark> says "this text is highlighted/relevant in the surrounding context", which is far more meaningful than a yellow-background CSS class. <kbd>, <samp>, and <var> form a trio for technical writing: keyboard input, sample program output, and mathematical or programming variables. <dfn> marks the first occurrence of a term being defined, which screen readers can use to navigate jargon. Reach for these before wrapping text in a generic <span> because they carry meaning to assistive tech, search engines, and even browsers' reader modes.
<!-- <video> with multiple source formats (browser picks the first one it
can play), captions, and an in-page poster image. -->
<video
controls
width="640"
height="360"
poster="thumbnail.jpg"
preload="metadata"
crossorigin="anonymous"
>
<source src="intro.webm" type="video/webm">
<source src="intro.mp4" type="video/mp4">
<track
kind="captions"
src="intro.en.vtt"
srclang="en"
label="English"
default
>
<track
kind="captions"
src="intro.es.vtt"
srclang="es"
label="Spanish"
>
<p>
Your browser does not support HTML5 video.
<a href="intro.mp4">Download the video</a> instead.
</p>
</video>
<!-- <audio> works the same way. preload values: none | metadata | auto. -->
<audio controls preload="metadata">
<source src="podcast.opus" type="audio/opus">
<source src="podcast.mp3" type="audio/mpeg">
Your browser does not support HTML5 audio.
</audio>
<!-- Tips:
- Always provide at least one captions track for video.
- preload="metadata" is the default when omitted; use "none" to save
bandwidth on lazy-loaded media.
- The fallback content inside <video>/<audio> renders only when the
browser cannot play any of the listed sources. -->Multi-source media elements let the browser pick the best format it can play; list more efficient codecs first (WebM/Opus) and fall back to MP4/MP3. The <track> element adds captions and subtitles via WebVTT files, which is a baseline accessibility requirement, not a nicety; mark the default language with the default attribute. The preload attribute controls how much of the media the browser pre-fetches: none saves bandwidth for off-screen players, metadata (the implicit default) loads just enough to show duration and dimensions, auto lets the browser load as much as it wants. Always include fallback content (a download link or a paragraph) inside the element so users on legacy browsers still see something.
