I’m constantly surprised by the native HTML spec. New features are regularly added, and I often stumble on existing, handy elements. While often not as versatile as their JS counterparts, using them avoids bloating your app with extra Javascript libraries or CSS hacks.
If this article helps just a single developer avoid an unnecessary Javascript dependency, I’ll be happy. Native HTML can handle plenty of features that people typically jump straight to JS for (or otherwise over-complicate).
I cover some great HTML elements in this article — modals, accordions, live range previews, progress bars and more. You might already know some of these, but I bet there’s something new here for you too.
Note: A few examples use a sprinkle of Javascript in their
oninput
properties. Please forgive me for this 😅. The elements are self-contained enough that I consider them ~HTML.
dialog
)I’ve led with native HTML modals since I think they’re a common pattern in many apps. People tend to reach directly to Javascript for modals, unaware of the powerful native option.
HTML modals — represented by <dialog>
— have extensive support for styling, controls etc, and in my opinion, are one of the most slept-on native HTML elements.
By leveraging the Popover API, dialogs can be opened and closed in native HTML; Opened by clicking a button with a popovertarget
referencing the <dialog>
element, and closed by clicking a similar button with popovertargetaction="hide"
, or pressing the esc key.
You can also open / close dialogs in Javascript by calling
.showModal()
&.closeModal()
.
Click the button below for an example:
<button popovertarget="modal">Open Modal</button>
<dialog id="modal" popover="auto" class="max-w-2xl backdrop:backdrop-blur-sm">
<p>Click the "X" to close me.</p>
<button popovertarget="modal" popovertargetaction="hide">x</button>
</dialog>
The entire modal is native HTML. Pressing the [x]
to close the modal uses the Popover API (or you can use the esc key).
A few things to note:
popovertarget="modal"
, matching the dialog’s ID, and the dialog has popover="auto"
. With these two parameters set, clicking the button will open the dialog.popovertarget="modal"
and popovertargetaction="hide"
, closing the modal when clicked.backdrop:
Tailwind CSS selector. For regular CSS, you can use the .modal::backdrop
pseudoclass.Originally, to open the
<dialog>
, I used a button withonclick="document.getElementById('modal').showModal()"
. A very smart reader pointed out though that the popover spec supports opening dialogs with native HTML.Thanks to Matthew for the tip!
MDN Docs (dialog) // Caniuse (96%)
details
and summary
)Accordions are another classic component that you can easily build with plain HTML. By combining the <details>
and <summary>
tags you can do some pretty cool things.
Here’s a basic example:
<details>
<summary>Native HTML Accordion (click to expand)</summary>
You can build accordions/disclosures with plain HTML, just like this one!
</details>
You can also supply the open
property to default the <details>
element open (it can still be collapsed):
details
accordion open by default with the open
property.
<details open>...</details>
You can also create exclusive accordions by supplying a matching name
parameter to multiple <details>
elements. This means only one group from the accordion can be open at a time:
<details name="group">
<summary>Frogs</summary>
🐸
</details>
<details name="group">
<summary>Dogs</summary>
🐶
</details>
<details name="group">
<summary>Cogs</summary>
⚙️
</details>
MDN Docs (details) // Caniuse (97%)
HTML range inputs are nothing new. By default they look like this, , and allow users to input a value on a sliding scale.
You can extend the functionality of the basic range input by adding tickmarks and a live value output.
datalist
)By supplying a <datalist>
to a range input, you can render tickmarks at each value step and clamp the input value to one of the datalist options.
<input type="range" min="1" max="5" list="steps" />
<datalist id="steps">
<option>1</option>
<option>2</option>
<option>3</option>
</datalist>
By adding a tiny bit of Javascript to the range input, you can easily add a live display of the input value. Credit goes to this excellent StackOverflow answer for figuring this out!
<input
type="range"
value="50"
min="1"
max="100"
oninput="this.nextElementSibling.value = this.value"
/>
<output>50</output>
MDN Docs (range) // Caniuse (98%)
progress
)These days, browsers support a basic, fully native HTML progress bar:
<progress value="6" max="10"></progress>
With some styling, you can make a progress bar look like the one below. I’ve used TailwindCSS for the styling, but you can apply them directly with CSS for the same result:
<progress
class="[&::-webkit-progress-bar]:rounded-lg [&::-webkit-progress-value]:rounded-lg [&::-webkit-progress-bar]:bg-stone-300 [&::-webkit-progress-value]:bg-stone-400 [&::-moz-progress-bar]:bg-stone-400"
value="6"
max="10"
></progress>
You can learn more about styling the <progress>
element with Tailwind CSS from this GitHub thread.
MDN Docs (progress) // Caniuse (97%)
datalist
and optgroup
)You can enhance the basic HTML list input to make it searchable or grouped with the <datalist>
and <optgroup>
elements.
datalist
<label for="browser">Type something to search the list:</label>
<input list="browser-names" name="browser" id="browser" />
<datalist id="browser-names">
<option value="Edge" />
<option value="Firefox" />
<option value="Chrome" />
<option value="Opera" />
<option value="Safari" />
</datalist>
MDN Docs (datalist) // Caniuse (97%)
optgroup
<label for="food">Choose a food:</label>
<select class="border rounded-md" name="food" id="food">
<optgroup label="Fruits">
<option value="apple">🍏 Apple</option>
</optgroup>
<optgroup label="Vegetables">
<option value="eggplant">🍆 Eggplant</option>
</optgroup>
<optgroup label="Desserts">
<option value="doughnut">🍩 Doughnut</option>
</optgroup>
</select>
MDN Docs (optgroup) // Caniuse (96%)
kbd
)The <kbd>
element is an HTML-native representation of keycombos. It typically appears similar to a <code>
element, but it’s more semantic and, being a distinct element, you can style it globally.
⌘ + P
<kbd>⌘ + P</kbd>
Globally targeting the <kbd>
tag can simplify your styling — you don’t have to add an extra class or component. Just style <kbd>
directly. It’s also more semantically correct to use <kbd>
for keyboard combinations, versus littering your pages with <span>
elements.
I’ve styled the <kbd>
tag to look like this: ⌘ + P
MDN Docs (kbd) // Caniuse (96%)
figcaption
)Browsers natively support captioned images with <figure>
and <figcaption>
. You might find it easier to use these elements instead of implementing the same thing yourself.
Here’s an example:
<figure>
<img src="image.jpg" alt="..." />
<figcaption>My picture</figcaption>
</figure>
MDN Docs (figcaption) // Caniuse (96%)
I hope you found this article useful!
I’m constantly surprised by what I can build with plain, native HTML, and it’s always satisfying ripping out a JS package I’ve replaced with a simple HTML tag.