<form>
	<div class="steps">
		<div class="steps__step" data-step="0">
			<div class="steps__step-number">1</div>
			<div class="steps__step-name">About You</div>
		</div>
		<div class="steps__connector"></div>
		<div class="steps__step" data-step="1">
			<div class="steps__step-number">2</div>
			<div class="steps__step-name">About Book</div>
		</div>
		<div class="steps__connector"></div>
		<div class="steps__step" data-step="2">
			<div class="steps__step-number">3</div>
			<div class="steps__step-name">Review</div>
		</div>
		<div class="steps__connector"></div>
		<div class="steps__step" data-step="3">
			<div class="steps__step-number">4</div>
			<div class="steps__step-name">Signing</div>
		</div>
		<div class="steps__connector"></div>
		<div class="steps__step" data-step="4">
			<div class="steps__step-number">5</div>
			<div class="steps__step-name">Contract</div>
		</div>
	</div>
	<div class="btn-group">
		<button class="btn" type="button" data-action="prev" disabled>Previous</button>
		<button class="btn" type="button" data-action="next">Next</button>
	</div>
</form>
* {
	border: 0;
	box-sizing: border-box;
	margin: 0;
	padding: 0;
}
:root {
	--hue: 223;
	--bg: hsl(var(--hue),10%,90%);
	--fg: hsl(var(--hue),10%,10%);
	--primary: hsl(var(--hue),90%,30%);
	--trans-dur: 0.3s;
	--trans-timing: cubic-bezier(0.65,0,0.35,1);
	font-size: calc(16px + (48 - 16) * (100vw - 280px) / (3840 - 280));
}
body,
button {
	font: 1em/1.5 "DM Sans", sans-serif;
}
body {
	background-color: var(--bg);
	color: var(--fg);
	display: flex;
	height: 100vh;
}
form {
	container: form / inline-size;
	margin: auto;
	padding: 1.5em;
	width: 100%;
	max-width: 36em;
}
.btn {
	background-color: var(--primary);
	border-radius: 0.25em;
	color: hsl(0,0%,100%);
	cursor: pointer;
	display: block;
	padding: 0.375em 0.75em;
	transition:
		background-color var(--trans-dur) var(--trans-timing),
		opacity var(--trans-dur) var(--trans-timing);
	width: 100%;
	-webkit-appearance: none;
	appearance: none;
	-webkit-tap-highlight-color: transparent;

	&:disabled {
		cursor: not-allowed;
		opacity: 0.5;
	}
	&:not(:disabled):hover {
		background: hsl(var(--hue),90%,10%);
	}
	&-group {
		display: flex;
		justify-content: center;
		gap: 0.75em;
		margin-top: 1.5em;
	}
}
.steps {
	// --trans-dur: 0.15s;
	background-color: hsl(0,0%,100%);
	border-radius: 0.75em;
	display: flex;
	padding: 1.5em;
	flex-direction: column;
	justify-content: center;
	width: 100%;

	&__connector,
	&__step {
		position: relative;
	}
	&__connector {
		background-color: hsl(var(--hue),10%,80%);
		margin-inline-start: 0.75em;
		width: 0.125em;
		height: 1.25em;
		transform: translateX(-50%);
		transition: background-color var(--trans-dur);

		&:before {
			background-color: var(--primary);
			content: "";
			display: block;
			width: 100%;
			height: 100%;
			transform: scale(1,0);
			transform-origin: 50% 0;
			transition:
				background-color var(--trans-dur),
				transform var(--trans-dur) var(--trans-timing);
		}
	}
	&__step {
		display: flex;
		align-items: center;
		flex-shrink: 0;
		z-index: 1;

		&-name {
			color: hsl(var(--hue),10%,50%);
			font-size: 0.75em;
			line-height: 2;
			transition:
				color var(--trans-dur) var(--trans-timing),
				font-weight var(--trans-dur) var(--trans-timing);
		}
		&-number {
			background-color: hsl(var(--hue),10%,80%);
			color: hsl(0,0%,100%);
			border-radius: 50%;
			margin-inline-end: 0.5em;
			text-align: center;
			width: 1.5em;
			height: 1.5em;
			transition:
				background-color var(--trans-dur) var(--trans-timing),
				box-shadow var(--trans-dur) var(--trans-timing);
		}
		&--current &-name,
		&--done &-name {
			color: hsl(var(--hue),10%,10%);
			font-weight: 700;
		}
		&--current &-number,
		&--done &-number {
			background-color: var(--primary);
		}
		&--current &-number,
		&--current &-name {
			transition-delay: var(--trans-dur);
		}
		&--current &-number {
			box-shadow: 0 0 0 0.125em hsla(var(--hue),90%,30%,0.4);
		}
	}
	&__step--done + &__connector {
		&:before {
			transform: scale(1,1);
		}
	}
}

/* Change layout depending on form width */
@container form (min-width: 30em) {
	.btn {
		width: auto;
	}
	.steps {
		flex-direction: row;
		align-items: center;
		padding: 1.5em 2.25em 2.25em 2.25em;

		&__connector {
			margin-inline-start: 0;
			width: 100%;
			height: 0.125em;
			transform: translateY(-50%);

			&:before {
				transform: scale(0,1);
				transform-origin: 0 50%;

				[dir="rtl"] & {
					transform-origin: 100% 50%;
				}
			}
		}
		&__step {
			&-name {
				position: absolute;
				top: 100%;
				left: 50%;
				text-align: center;
				width: 6em;
				transform: translateX(-50%);
			}
			&-number {
				margin-inline-end: 0;
			}
		}
	}
}

/* Dark theme */
@media (prefers-color-scheme: dark) {
	:root {
		--bg: hsl(var(--hue),10%,10%);
		--fg: hsl(var(--hue),10%,90%);
		--primary: hsl(var(--hue),90%,70%);
	}
	.btn {
		color: hsl(var(--hue),10%,10%);

		&:not(:disabled):hover {
			background: hsl(var(--hue),90%,50%);
		}
	}
	.steps {
		background-color: hsl(var(--hue),10%,20%);

		&__connector {
			background-color: hsl(var(--hue),10%,40%);
		}
		&__step {
			&-name {
				color: hsl(var(--hue),10%,50%);
			}
			&-number {
				background-color: hsl(var(--hue),10%,40%);
				color: hsl(var(--hue),10%,20%);
			}
			&--current &-name,
			&--done &-name {
				color: hsl(var(--hue),10%,90%);
			}
			&--current &-number {
				box-shadow: 0 0 0 0.125em hsla(var(--hue),90%,70%,0.4);
			}
		}
	}
}
View Compiled
window.addEventListener("DOMContentLoaded",() => {
	const steps = new StepIndicator(".steps");
});

class StepIndicator {
	/** Element used for this step indicator */
	el: HTMLElement | null;
	/** Number of steps */
	steps = 5;

	private _step = 0;
	get step(): number {
		return this._step;
	}
	set step(value: number) {
		this.displayStep(value);
		this._step = value;
		this.checkExtremes();
	}
	/**
	 * @param el CSS selector of the step indicator element
	 */
	constructor(el: string) {
		this.el = document.querySelector(el);
		document.addEventListener("click",this.clickAction.bind(this));
		this.displayStep(this.step);
		this.checkExtremes();
	}
	/**
	 * @param e Click event
	 */
	clickAction(e: Event): void {
		const button = e.target as HTMLButtonElement;
		const actionName = button?.getAttribute("data-action");

		if (actionName === "prev") {
			this.prev();
		} else if (actionName === "next") {
			this.next();
		}
	}
	/** Go to the previous step. */
	prev(): void {
		if (this.step > 0) {
			--this.step;
		}
	}
	/** Go to the next step. */
	next(): void {
		if (this.step < this.steps - 1) {
			++this.step;
		}
	}
	/** Disable the Previous or Next button if hitting the first or last step. */
	checkExtremes(): void {
		const prevBtnEl = document.querySelector(`[data-action="prev"]`) as HTMLButtonElement;
		const nextBtnEl = document.querySelector(`[data-action="next"]`) as HTMLButtonElement;

		if (prevBtnEl) {
			prevBtnEl.disabled = this.step <= 0;
		}
		if (nextBtnEl) {
			nextBtnEl.disabled = this.step >= this.steps - 1;
		}
	}
	/**
	 * Update the indicator for a targeted step.
	 * @param targetStep Index of the step
	 */
	displayStep(targetStep: number) {
		const current = "steps__step--current";
		const done = "steps__step--done";

		for (let s = 0; s < this.steps; ++s) {
			const stepEl = this.el?.querySelector(`[data-step="${s}"]`);
			stepEl?.classList.remove(current,done);

			if (s < targetStep) {
				stepEl?.classList.add(done);
			} else if (s === targetStep) {
				stepEl?.classList.add(current);
			}
		}
	}
}
View Compiled

External CSS

  1. https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&amp;display=swap

External JavaScript

This Pen doesn't use any external JavaScript resources.