typescript
45 lines · 9 steps
Lightening and darkening hex colors in TypeScript
A small color toolkit parses hex strings into RGB, shifts each channel, and formats the result back to hex.
Explained by
highlit
1type RGB = { r: number; g: number; b: number };
2
3function parseHex(hex: string): RGB {
4 const normalized = hex.replace(/^#/, "").trim();
5 const expanded =
6 normalized.length === 3
7 ? normalized.split("").map((c) => c + c).join("")
8 : normalized;
9
10 if (!/^[0-9a-fA-F]{6}$/.test(expanded)) {
11 throw new Error(`Invalid hex color: ${hex}`);
12 }
13
14 const value = parseInt(expanded, 16);
15 return {
16 r: (value >> 16) & 0xff,
17 g: (value >> 8) & 0xff,
18 b: value & 0xff,
19 };
20}
21
22function toHex({ r, g, b }: RGB): string {
23 const channel = (n: number) =>
24 Math.round(clamp(n, 0, 255)).toString(16).padStart(2, "0");
25 return `#${channel(r)}${channel(g)}${channel(b)}`;
26}
27
28function clamp(n: number, min: number, max: number): number {
29 return Math.min(max, Math.max(min, n));
30}
31
32function adjust(hex: string, amount: number): string {
33 const { r, g, b } = parseHex(hex);
34 const shift = (channel: number) =>
35 amount >= 0
36 ? channel + (255 - channel) * amount
37 : channel * (1 + amount);
38
39 return toHex({ r: shift(r), g: shift(g), b: shift(b) });
40}
41
42const lighten = (hex: string, ratio: number) => adjust(hex, Math.abs(ratio));
43const darken = (hex: string, ratio: number) => adjust(hex, -Math.abs(ratio));
44
45export { parseHex, toHex, lighten, darken };
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Bit-shifting and masking cleanly slice a packed integer into separate byte channels.
- 2Validating input with a regex before parsing turns malformed data into a clear error instead of silent garbage.
- 3A single core function can spawn readable specializations by fixing the sign of one argument.
Related explainers
typescript
interface RunningStats { count: number; total: number; average: number;
Streaming running averages in TypeScript
generators
streaming
incremental-aggregation
Intermediate
6 steps
php
<?php namespace App\Http;
HTTP content negotiation in PHP
http
content-negotiation
parsing
Intermediate
9 steps
typescript
import { IsEmail, IsNotEmpty, IsOptional,
How validation groups reuse one DTO in NestJS
validation
dto
decorators
Intermediate
9 steps
typescript
import { InjectionToken, inject, Provider, isDevMode } from '@angular/core'; import { WINDOW } from './window.token'; export interface AnalyticsConfig {
Layered config with an Angular InjectionToken
dependency-injection
configuration
factory-provider
Intermediate
8 steps
python
import re from dataclasses import dataclass, field
Building a table of contents from Markdown
regular-expressions
parsing
slugification
Intermediate
9 steps
javascript
const IBAN_LENGTHS = { DE: 22, FR: 27, GB: 22, ES: 24, IT: 27, NL: 18, BE: 16, CH: 21, AT: 20, PT: 25, };
How IBAN validation works in JavaScript
validation
checksum
modular-arithmetic
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/lightening-and-darkening-hex-colors-in-typescript-explained-typescript-6ed1/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.