Your Website does not need JavaScript

JS is useful
but often overused

HTML & CSS are lighter

  • No JS
  • No build tools
  • No npm packages
  • Just HTML & CSS

HTML Tricks

  • Content Targeting
  • Accordians
  • Form Validations

Content Targeting

<!-- Links to another page or website -->
<a href="/about">Other page</a>
<a href="https://google.com">Other website</a>

<!-- A File or Resource -->
<a href="/img/my_image.jpg" download>Download Image</a>
<a href="/resources/info.pdf">View PDF</a>

<!-- An ID -->
<a href="#main">Skip to main content</a>
<a href="/about#contact">Contact the team</a>
.my_element:target {
	background: pink;
}
<body>
	<header><nav>
		<a href="#home">Home</a>
		...
		<a href="#contact">Contact</a>
	</nav></header>
	<main>
		<section id="home"></section>
		...
		<section id="contact"></section>
	</main>
</body>
section {
	display: none;

	&:target {
		display: block;
	}
}
chrome icons
2010
edge icons
2015
safari icons
2008
firefox icons
2006
ie icons
2011

Full Disclosure…

Accordians

<details>
	<summary>This bit is clickable to expand/collapse</summary>
	<p>This content is hidden but can then be expanded or re-collapsed.</p>
</details>
<details>
	<summary>Why is HTML better than JS?</summary>
	<p>HTML functionality...</p>
	...
</details>
<details>
	<summary>Why is CSS better than JS?</summary>
	<p>CSS is built for styling...</p>
</details>
chrome icons
2011
edge icons
2020
safari icons
2012
firefox icons
2016

Full Disclosure…

Form Validations

Required Fields

<input name="email" required />

Data Format

<input type="date" name="due_date">

<input type="tel" name="phone" pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}" />
chrome icons
2010
edge icons
2015
safari icons
2010
firefox icons
2011
ie icons
2012
chrome icons
2011
edge icons
2015
safari icons
2017
firefox icons
2011
ie icons
2012

Full Disclosure…

CSS Tricks

  • Nesting
  • Range Slider
  • Testimonials
  • Toggle Switch
  • Image Carousel

Nesting

div {
	& p {
		color: red;
	}
}
chrome icons
2023
edge icons
2023
safari icons
2023
firefox icons
2023

Range Slider

<input type="range" min="0" max="10" />
div:has(p:nth-child(1)) {
	--children: 1;
}
...
div:has(p:nth-child(4)) {
	--children: 4;
}

div:has(p:nth-child(5)) {
	--children: 5;
}
<input type="range" min="0" max="10" list="options" />
<datalist id="options">
	<option value="0">Not good</option>
	<option value="5">🤷‍♀️</option>
	<option value="10">Stupendous</option>
</datalist>
	<input 
		type="range" 
		min="0" 
		max="10" 
		list="js_skills" 
	/>
	<datalist id="js_skills">
		<option value="0">Never used it before</option>
		<option value="2">Used it once or twice</option>
		<option value="4">Can follow a tutorial</option>
		<option value="6">Fine as long as it works</option>
		<option value="8">Use it regularly</option>
		<option value="10">I use JS for everything</option>
	</datalist>
	
&:has(input[type="range"] + datalist option:nth-child(1)) {
	--opts: 1;
}

&:has(input[type="range"] + datalist option:nth-child(2)) {
	--opts: 2;
}

...

&:has(input[type="range"] + datalist option:nth-child(10)) {
	--opts: 10;
}
	
input[type="range"] {
	width: calc(100% - (100% / var(--opts)));

	& + datalist {
		display: grid;
		grid-template-columns: 50px repeat(var(--opts),1fr 2em);

		& option {
			grid-column-end: span 3;
			grid-column-start: var(--column);

			&:nth-child(2n) {
				grid-row-start: 2;
			}
		}
	}
}
chrome icons
2010
edge icons
2015
safari icons
2008
firefox icons
2013
ie icons
2012
chrome icons
2018
edge icons
2020
safari icons
2019
firefox icons
2023
chrome icons
2022
edge icons
2022
safari icons
2022
firefox icons
2023

Testimonials

<input type="radio" id="t_1" name="testimonials" />
<label for="t_1">Quote 1</label>
<blockquote>
	<!-- Quote here -->
</blockquote>

.testimonials {
	display: grid;
	grid-template-rows: 1fr auto;
	grid-template-columns: 1fr repeat(var(--num_test), auto) 1fr;
	justify-content: center;

	&:has(blockquote:nth-of-type(1)) {
		--num_test: 1;
	}

	...
}
	blockquote {
		grid-column: 1 / -1;
		grid-row: 1 / 2;
	}

	label {
		order: 2;
		grid-row: 2;
	}
blockquote {
	pointer-events: none;
	opacity: 0;
}		

input[type="radio"]:checked {
	& + label + blockquote {
		opacity: 1;
		pointer-events: auto;
	}
}
label {
	position: relative;
	cursor: pointer;

	&:before, &:after {
		height: 20px;
		width: 20px;
		border-radius: 50%;
		border: 2px solid var(--green);
	}
} 
	
label {
	&:after {
		background: radial-gradient(
			circle,  
			var(--green) calc(100% - 6px), 
			transparent calc(100% - 6px)
		);
		position: absolute;
	}
} 
	
	label:first-of-type {
		grid-column: 2;
	}
	
	.testimonials:not(:has(input[type="radio"]:checked)) {
		& blockquote:first-of-type {
			opacity: 1;
			pointer-events: auto;
		}
	}
chrome icons
2022
edge icons
2022
safari icons
2022
firefox icons
2023
chrome icons
2021
edge icons
2021
safari icons
2015
firefox icons
2020
chrome icons
2017
edge icons
2017
safari icons
2017
firefox icons
2017
ie icons
2017

Full Disclosure…

Toggle Switch

<div class="toggle">
	<input id="off" type="radio" name="switch" value="off" checked />
	<label for="off">Off</label>
	<input id="on" type="radio" name="switch" value="on" />
	<label for="on">On</label>
	<span class="switch"></span>
</div>
.toggle {
	display: flex;
	justify-content: center;
	align-items: center;
}

input[type="radio"][value="on"] + label {
	order: 2;
}
	.switch::before {
		position: absolute;
		top: -3px;
		left: -3px;
		width: 1.7em;
		height: 1.7em;
		border: 3px solid currentcolor;
		border-radius: 50%;
		background: var(--background);
	}
	input[type="radio"][value="on"]:checked ~ .switch {
		background: currentcolor;

		&::before {
			right: -3px;
			left: auto;
		}
	}
	input[type="radio"] + label::before {
		position: absolute;
		inset: 0;
		z-index: 4;
	}

	input[type="radio"][value="on"] + label::before {
		right: 0;
		left: calc(-1 * (1ch + 2.7em));
	}

	input[type="radio"][value="off"] + label::before {
		right: calc(-1 * (1ch + 2.7em));
		left: 0;
	}
input[type="radio"]:checked + label::before {
	content: '';
}
body:has(.theme_toggle input[type="radio"][value="light"]:checked) {
	--background: #f7f0eb;
	--neutral: #0d0d0d;
	...
}

body:has(.theme_toggle input[type="radio"][value="dark"]:checked) {
	--background: #0d0d0d;
	--neutral: #f7f0eb;
	...
}
chrome icons
2022
edge icons
2022
safari icons
2022
firefox icons
2023

Full Disclosure…

<input type="radio" name="images" value="i_1" id="img_1" checked />
<label for="img_1">Image 1</label>
<div class="image">
	<img src="img_1.jpg" alt="" />
</div>

.carousel {
	display: grid;
	grid-template-columns: 4em 1fr 4em;
	grid-template-rows: 300px;
	grid-template-areas: 'previous image next';
	overflow: hidden;
	max-width: 100%;
}

.image {
	grid-area: image;
	opacity: 0;
}

label {
	display: none;
}
	input[type="radio"]:checked  + label + .image {
		opacity: 1;
	}

	.carousel:not(:has(input[type="radio"]:checked)) {
		& input[type="radio"]:first-of-type + label + .image {
			opacity: 1;
		}
	}
	input[type="radio"]:checked + label + .image + input[type="radio"] + label {
		display: block;
		grid-area: next;
		
		&::before {
			content: '➡️';
		}
	}

	input[type="radio"]:not(:checked):has(+ label + .image + input:checked) + label {
		display: block;
		grid-area: previous;

		&::before {
			content: '⬅️';
		}
	}
chrome icons
2017
edge icons
2017
safari icons
2017
firefox icons
2017
ie icons
2017
chrome icons
2022
edge icons
2022
safari icons
2022
firefox icons
2023

Full Disclosure…

nojs.amyskapers.dev

JS is useful but not the answer

Can you do it with HTML?

Can you do it with CSS?

Can JS make it better?

Can some JS be removed?

Do we really need to track this?

Questions?

kapers.dev/nojs

Thank You👏