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

Walkthrough

Space play ←→ step click any line
Three takeaways
  1. 1Bit-shifting and masking cleanly slice a packed integer into separate byte channels.
  2. 2Validating input with a regex before parsing turns malformed data into a clear error instead of silent garbage.
  3. 3A single core function can spawn readable specializations by fixing the sign of one argument.

Related explainers

typescript
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';
import localeDe from '@angular/common/locales/de';

Locale-aware bootstrapping in Angular

i18n localization dependency-injection
Intermediate 8 steps
typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
 

Validating env config at boot in NestJS

configuration schema-validation environment-variables
Intermediate 8 steps
ruby
class UserAgentParser
  BROWSERS = [
    [/Edg\/([\d.]+)/, "Edge"],
    [/OPR\/([\d.]+)/, "Opera"],

Parsing user-agent strings in Ruby

regex pattern-matching lookup-tables
Intermediate 8 steps
javascript
function evaluate(expression) {
  const tokens = tokenize(expression);
  let pos = 0;
 

Building a recursive descent calculator

parsing recursion operator-precedence
Intermediate 8 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { InjectRepository } from '@nestjs/typeorm';

A cache-aside country lookup in NestJS

cache-aside dependency-injection batch-lookup
Intermediate 8 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core';
 
interface Preferences {
  theme: 'light' | 'dark';

A signal-based preferences store in Angular

signals state-management persistence
Intermediate 7 steps

Share this explainer

Here's the card — post it anywhere.

Lightening and darkening hex colors in TypeScript — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code