You Might Not Need a Library

JavaScript and CSS improves on most sunny days. They add support for features that were previously done by libraries, sometimes even mimicking the API. And sometimes there was a native feature all along that has slipped past many of us. This page aims to provide a collection of things like that. Not a complete reference of everything that you can do, but a list of alternative ways that are sometimes overlooked.

Beware that these examples include code that might be unsupported in some browsers with IE and Safari being the most often victims. Some of the suggestions might be found controversial, but that doesn’t mean they should be hidden. Learn all the possibilities and choose according to circumstances.

Table of Contents

Raw JavaScript can do it

Selecting elements

The Selectors API let’s you select the elements just like in CSS and jQuery.

const element = document.querySelector('p.charming')  // returns first match
const list = document.querySelectorAll('p.strange')  // return NodeList
jQuery equivalent
const element = $('p.charming').first()
const list = $('p.charming')

Note: the jQuery examples end up with a jQuery object not a raw element or NodeList so they are technically not equivalent. But it seemed silly to add [0] as that’s not used if you actually work with jQuery.

Some people like to alias these to $('p.charming') and $$('p.strange'), but it might be mistaken for jQuery.

Remember that NodeList has a forEach method and the length property. You can loop over it however you like without creating an array or retrieving Array.prototype.forEach.

Children, descendants

The same API works for selecting inside an element.

const descendantElement = element.querySelector('p.charming')
const list = element.querySelectorAll('p.strange')

const children = element.children // not new, included for completeness like .parentNode later on
// to select among direct descendants use :scope pseudo-class that refers to element
const selectedChildren = element.querySelectorAll(':scope > p')
jQuery equivalent
const childElement = $(element).find('p.charming').first()
const list = $(element).find('p.strange')
const children = $(element).children()
const selectedChildren = $(element).children('p')

Parents, ancestors

const parent = element.parentNode // there is also .parentElement if you want to get confused

// go up the tree until something matches
const ancestor = element.closest('div.container')
jQuery equivalent
const parent = $(element).parent()
const ancestor = $(element).closest('div.container')

The old selecting

Remember that these still work and are faster than anything else.

document.getElementsByTagName ('input')
document.getElementByName('attachments[]')  // let me know if you have ever used this

// or search descendants in an element
element.getElementsByTagName ('label')

DOM manipulation

// move (or insert if it's a new node)
parent.append(childA, childB, 'just a text string', childC)
parent.prepend(childX, childY)

sibling.after(aSx, aSy)

// remove
jQuery equivalent
$(parent).append(child) // or $(child).appendTo(parent)
$(parent).append(childA, childB, 'just a text string', childC)
$(parent).prepend(childX, childY) // $([childX,childY]).prependTo(parent)

$(sibling).after(anotherSibling) // or $(anotherSibling).insertAfter(sibling)
$(sibling).after(aSx, aSy) // or $([aSx, aSy]).insertBefore(sibling)
$(sibling).before(anotherSibling) // or $(anotherSibling).insertBefore(sibling)

Equivalent in old/supported JavaScript
parent.appendChild(child) // can't insert many at once
parent.insertBefore(child, null)  // yup, this is how you prepended

parent.insertAfter(newSibling, sibling)
parent.insertBefore(newSibling, sibling)


Element manipulation


element.classList.add('strange')  // or element.classList.toggle('strange', true)
element.classList.add('stranger', 'charming')

// when you're tired of spelling element.classList
const cl = element.classList

cl.remove('stranger')  // or cl.toggle('stranger', false)
cl.replace('strange', 'strangest')

const hasClass = element.classList.contains('charming')
jQuery equivalent
$(element).addClass('strange') // or $(element).toggleClass('strange', true)
$(element).addClass('stranger charming')

// when you're tired of spelling $(element)
const el = $(element)

el.removeClass('stranger')  // or el.toggleClass('stranger', false)
el.switchClass('strange', 'strangest')

const hasClass = el.hasClass('charming')

Note: the toggle with a boolean value is used:

// to simplify this
if (condition)

// into this
element.classList.toggle('strange', condition)


element.dataset.myProperty = '123'  // sets data-my-property="123" on the element
const someData = element.dataset.stuff  // retrieves value of data-stuff attribute


GETting is easy these days.


// often you unpack the response immediatelly
	.then(response => response.json())

You can also do more complicated things with the Fetch API. But maybe that’s where you’d want a library…

And more…

If you are stuck on jQuery just for the convenience, remember that a lot of classic JS methods and properties are simple and usable like element.textContent, element.innerHTML, element.nextElementSibling,, getComputedStyle(element), element.getAttribute('some-attr'), …

You can read about a lot of that on YMNNJ.

You can do it without JS

Toggle styling

You can hide depending on value of checkbox. You can toggle the checkbox with a label

<label for="toggler1" class="toggler__label">Toggle here</label>
<input type="checkbox" class="toggler__input" id="toggler1" checked>
<div class="toggler__togglable">
	Togglable size

Here are the styles:

.toggler__label {
	cursor: pointer;

.toggler__input {
	display: none;

.toggler__togglable {
	font-size: 1.5rem;

.toggler__input:checked + .toggler__togglable {
	font-size: 2rem;
Togglable size

If you just want to show and hide a block of content right after your label you can use details.

Use the :target selector

While you don’t want the address bar and browser’s history littered on any changes of visual state, sometimes you do. Like when looking at a gallery of images. If so, you can select the relevant element using the :target pseudo-class.

<a href="#image1">
		src="" >
<a href="#image2">
		src="" >
<a href="#image3">
		src="" >
.gallery__image {
	width: 100px;
	height: 100px;
	object-fit: cover;

.gallery__image:target {
	width: 300px;
	height: auto;
	object-fit: none;

You can create both lightboxes and galleries like that. By the way that final example is from the February of 2012.


If you use HTML validation, you can also style your inputs using :valid, :invalid and other pseudo-classes.

<input class="validation__range" type="number" min="1" max="3" value="7">
<span class="validation__error">The number should be between 1 and 3.</span>

<input class="validation__invalid" type="email" placeholder="enter an email" required>
.validation__error {
	display: none;

.validation__range:out-of-range + .validation__error {
	display: inline;

input:required {
	border: 1px orange solid;

.validation__invalid:valid {
	border-bottom: 3px green solid;

The number should be between 1 and 3.

Display your head and the elements within

Many people are not aware that the elements like head, style, script and others are invisible just because the default stylesheet on their browser says so. You can view all of it inside the viewport if you want to.

Here’s an example styling to display style, link and script elements and their sources.

head, script, style, link {	display: block }
script, style {	white-space: pre }
link::before { content: attr(href) }
style::before { content: attr(src) }

And other possibilities

There is a million things that can be done with just CSS. Including games like this and this or even without any HTML.

Recall the toggling example above? You could as well use radio buttons instead and make yourself a carousel. You don’t even have to put every input just before the content, you can .carousel__input:nth-of-type(4) ~ .carousel__slide:nth-of-type(4) to have fourth input correspond to fourth slide. And in that way you refer to multiple elements that you style according to state of that input.

You only need HTML for some things

Details is the spoiler thing

Did you notice the jQuery examples on this page? They are pure HTML. It’s the details element.

	<summary>The summary or spoiler label or...</summary>
	The super thorough explanation

Displaying PDFs

If you want to display a preview of PDF file, a lot of browsers have an embedded PDF viewer. There are three ways to trigger that inline without any plugins.

<object data="test.pdf"></object>

<iframe src="test.pdf"></iframe>

<embed src="test.pdf"></embed>

And there are some things you don’t even need

Semicolons in JavaScript

Did you notice that none of the JavaScript examples had any semicolons? There is a feature called automatic semicolon insertion (ASI) in JS. You can read up on it yourself, but a general rule is that you don’t need to terminate your statements with a semicolon unless you start the next line with a [ or (.

Optional tags

Closing tags

Some the HTML tags are optional to close. It’s often to see <p> without a closing tag. But body and html are also optional to close. And is there ever a point to have </body> and </html> included in your document? It’s even optional to close head. You can just start the body.

Other tags that are closed automatically include li, option, dt, dd, tr, th, td, thead, tbody, tfoot. This is not a complete list but these are the common ones that are often more readable if just followed by the next sibling instead of closing tag.

Here is a nice example from WHATWG that shows how readable is a table without closing tags inside.

 <caption>37547 TEE Electric Powered Rail Car Train Functions (Abbreviated)
  <tr> <th>Function                              <th>Control Unit     <th>Central Station
  <tr> <td>Headlights                            <td><td><tr> <td>Interior Lights                       <td><td><tr> <td>Electric locomotive operating sounds  <td><td><tr> <td>Engineer's cab lighting               <td>                 <td><tr> <td>Station Announcements - Swiss         <td>                 <td></table>

Opening tags

Some of the opening tags are also optional. For example, many of us have written a table with tree such as this.

	<tr> <td>first   <td>second   <td>third
	<tr> <td>fourth  <td>fifth    <td>sixth

Have you noticed that the rows get wrapped in a tbody automatically? It turns out that both the opening and closing tags of tbody are optional - you don’t have to specify them if you want them left at the default positions.

And you actually don’t have to include <html>, <head> and <body>… with the caveat that you need to specify tag if you want any attributes there and you usually want <html lang=..>.

A more complete discussion on tag omission can be found here at WHATWG.

Some guidelines even suggest skipping all of the optional tags. I do the same unless I’m working with Vue.

Suggested resources

Here are some links that I find useful or just like. Check them out, you might find something useful.