npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

bdsg

v0.2.0

Published

Design system generation library with WCAG accessibility compliance

Readme

bdsg

npm version CI License: MIT

Design System Generation library. Algorithms for generating design tokens programmatically with WCAG accessibility compliance.

Table of Contents

Installation

bun add bdsg
# or
npm install bdsg

Quick Start

import {
  generatePalette,
  generateTypographyScale,
  generateSpacingScale,
  generateShadows,
  adjustColorForContrast,
} from "bdsg";

// Generate a color palette from a base color
const palette = generatePalette("#3B82F6", "primary");
console.log(palette.shades[500].value);      // "#3B82F6"
console.log(palette.shades[500].textColor);  // "#FFFFFF"

// Adjust a color to meet WCAG AA contrast requirements
const result = adjustColorForContrast("#87CEEB", "#FFFFFF", "AA");
console.log(result.adjusted);  // Darker blue that meets 4.5:1 ratio
console.log(result.ratio);     // 4.52

// Generate typography scale using perfect-fourth ratio
const typography = generateTypographyScale({
  base: 16,
  ratio: "perfect-fourth",
  stepsUp: 4,
  stepsDown: 2,
});
console.log(typography.tokens);
// [{ name: "xs", fontSize: 9, lineHeight: 1.7, ... }, ...]

// Generate spacing scale with Fibonacci progression
const spacing = generateSpacingScale({ base: 8, method: "fibonacci" });
console.log(spacing.tokens.map(t => t.formatted));
// ["0", "0.25rem", "0.5rem", "0.75rem", "1rem", ...]

// Generate Material Design-style shadows
const shadows = generateShadows({ style: "material", levels: 5 });
console.log(shadows.cssVariables);
// "--shadow-none: none;\n--shadow-sm: 0px 1px 2px..."

API Reference

Color Utilities

Convert colors between hex, RGB, and HSL color spaces.

import { hexToRgb, rgbToHex, hexToHsl, hslToHex } from "bdsg";

hexToRgb("#FF5733");           // { r: 255, g: 87, b: 51 }
hexToRgb("#F00");              // { r: 255, g: 0, b: 0 } (shorthand supported)
rgbToHex({ r: 0, g: 128, b: 255 });  // "#0080ff"
hexToHsl("#3B82F6");           // { h: 217, s: 91, l: 60 }
hslToHex({ h: 0, s: 100, l: 50 });   // "#ff0000"

OKLCH Color Space - ^0.2.0

OKLCH is a perceptually uniform color space that provides better gradient interpolation and more intuitive color manipulation.

import { hexToOklch, oklchToHex, interpolateOklch } from "bdsg";

// Convert between HEX and OKLCH
const oklch = hexToOklch("#3B82F6");
// { l: 0.623, c: 0.185, h: 259.5 }
// l: lightness (0-1), c: chroma (0+), h: hue (0-360)

oklchToHex({ l: 0.623, c: 0.185, h: 259.5 });  // "#3b82f6"

// Interpolate colors without "muddy middle"
const red = hexToOklch("#FF0000");
const green = hexToOklch("#00FF00");
const middle = interpolateOklch(red, green, 0.5);
// Produces vibrant yellow, not muddy brown like RGB interpolation

Why OKLCH?

  • Perceptually uniform: Equal lightness values look equally bright across all hues
  • No muddy gradients: Interpolation stays vibrant, avoiding the brown/gray zone
  • Intuitive: Hue, chroma, and lightness are independent

Gradients - ^0.2.0

Generate smooth color gradients using OKLCH interpolation with easing and hue direction control.

import {
  generateGradient,
  generateMultiStopGradient,
  toCssGradient,
  EASING
} from "bdsg";

// Simple two-color gradient
const gradient = generateGradient("#FF0000", "#0000FF", 5);
// ["#ff0000", "#c5007c", "#9100c9", "#5c00ed", "#0000ff"]

// With easing function
const smooth = generateGradient("#000000", "#FFFFFF", 5, {
  easing: EASING.easeInOut
});

// Control hue direction (shorter or longer path around color wheel)
const rainbow = generateGradient("#FF0000", "#FF8800", 5, {
  hueDirection: "longer"  // Takes the long way through blue/purple
});

// Multi-stop gradient
const sunset = generateMultiStopGradient([
  { color: "#FF0000", position: 0 },
  { color: "#FFFF00", position: 0.3 },
  { color: "#00FF00", position: 1 }
], 10);

// Generate CSS gradient string
const colors = generateGradient("#FF0000", "#0000FF", 3);
toCssGradient("linear", colors, 45);
// "linear-gradient(45deg, #ff0000, #800080, #0000ff)"

toCssGradient("radial", colors);
// "radial-gradient(circle, #ff0000, #800080, #0000ff)"

toCssGradient("conic", colors, 90);
// "conic-gradient(from 90deg, #ff0000, #800080, #0000ff)"

Available easing functions:

  • EASING.linear — Constant speed
  • EASING.easeIn — Slow start, accelerates
  • EASING.easeOut — Fast start, decelerates
  • EASING.easeInOut — Slow start and end

Hue direction options:

  • "shorter" — Takes shortest path around color wheel (default)
  • "longer" — Takes longer path for rainbow effects
  • "increasing" — Always increases hue
  • "decreasing" — Always decreases hue

Contrast

Calculate and validate WCAG 2.1 contrast ratios.

import {
  calculateContrast,
  getRelativeLuminance,
  meetsWCAG,
  getWCAGCompliance,
} from "bdsg";

// Calculate contrast ratio (1:1 to 21:1)
calculateContrast("#000000", "#FFFFFF");  // 21
calculateContrast("#767676", "#FFFFFF");  // 4.54

// Check WCAG compliance
meetsWCAG(4.5, "AA", "normal");   // true
meetsWCAG(4.5, "AAA", "normal");  // false (AAA requires 7.0)
meetsWCAG(3.0, "AA", "large");    // true (large text only needs 3.0)

// Get full compliance info
getWCAGCompliance(4.5, "normal");
// { ratio: 4.5, AA: true, AAA: false, level: "AA" }

WCAG Requirements: | Level | Normal Text | Large Text | |-------|-------------|------------| | AA | 4.5:1 | 3.0:1 | | AAA | 7.0:1 | 4.5:1 |

Color Adjustment

Automatically adjust colors to meet accessibility requirements.

import { adjustColorForContrast, generateAccessibleVariations } from "bdsg";

// Adjust color to meet WCAG AA on white background
const result = adjustColorForContrast("#87CEEB", "#FFFFFF", "AA", "normal");
// {
//   original: "#87CEEB",
//   adjusted: "#1A6B8A",  // Darkened to meet contrast
//   ratio: 4.52,
//   iterations: 8,
//   strategy: "lightness"
// }

// Generate accessible color variations
const variations = generateAccessibleVariations("#3B82F6", "#FFFFFF");
// {
//   base: "#2563EB",   // Adjusted base
//   light: "#60A5FA",  // Lighter variant
//   dark: "#1E40AF",   // Darker variant
//   text: "#FFFFFF"    // Best text color on base
// }

The algorithm:

  1. Preserves hue (brand identity)
  2. Adjusts lightness first (binary search)
  3. Reduces saturation if needed
  4. Falls back to black/white if impossible

Palette Generation

Generate 10-shade color palettes from a base color.

import { generatePalette, generatePaletteTokens } from "bdsg";

const palette = generatePalette("#3B82F6", "primary");

// Each shade includes value, text color, and contrast ratio
palette.shades[100];
// { value: "#DBEAFE", textColor: "#000000", contrastRatio: 17.4 }

palette.shades[900];
// { value: "#1E3A8A", textColor: "#FFFFFF", contrastRatio: 10.2 }

// Export as flat tokens
const tokens = generatePaletteTokens("#3B82F6", "primary");
// [
//   { name: "primary-50", value: "#EFF6FF", textColor: "#000000", ... },
//   { name: "primary-100", value: "#DBEAFE", ... },
//   ...
// ]

Shade lightness targets: | Shade | Lightness | |-------|-----------| | 50 | 97% | | 100 | 93% | | 200 | 85% | | 300 | 75% | | 400 | 60% | | 500 | 50% (base)| | 600 | 42% | | 700 | 35% | | 800 | 27% | | 900 | 20% |

Typography Scale

Generate font sizes using musical ratio progressions.

import { generateTypographyScale, TYPOGRAPHY_RATIOS } from "bdsg";

const scale = generateTypographyScale({
  base: 16,                    // Base font size in px
  ratio: "perfect-fourth",     // 1.333 ratio
  stepsUp: 6,                  // Sizes above base
  stepsDown: 2,                // Sizes below base
  baseLineHeight: 1.5,         // Line height for base
  unit: "rem",                 // Output unit
});

// Each token includes calculated line-height and letter-spacing
scale.tokens[0];
// {
//   name: "xs",
//   fontSize: 9,
//   lineHeight: 1.7,      // Looser for small text
//   letterSpacing: 0.02,  // Positive tracking
//   weight: 400
// }

scale.tokens[scale.tokens.length - 1];
// {
//   name: "4xl",
//   fontSize: 50,
//   lineHeight: 1.1,      // Tighter for display
//   letterSpacing: -0.02, // Negative tracking
//   weight: 700
// }

// Available ratios
TYPOGRAPHY_RATIOS["minor-second"];    // 1.067
TYPOGRAPHY_RATIOS["major-second"];    // 1.125
TYPOGRAPHY_RATIOS["minor-third"];     // 1.2
TYPOGRAPHY_RATIOS["major-third"];     // 1.25
TYPOGRAPHY_RATIOS["perfect-fourth"];  // 1.333
TYPOGRAPHY_RATIOS["augmented-fourth"]; // 1.414 (sqrt(2))
TYPOGRAPHY_RATIOS["perfect-fifth"];   // 1.5
TYPOGRAPHY_RATIOS["golden-ratio"];    // 1.618

Spacing Scale

Generate spacing values using mathematical progressions.

import { generateSpacingScale, SPACING_PRESETS } from "bdsg";

// Fibonacci progression (organic, natural feel)
const fibonacci = generateSpacingScale({
  base: 8,
  method: "fibonacci",
  steps: 10,
});
// Values: [0, 4, 8, 12, 16, 28, 44, 72, 116, 188]

// Linear progression (consistent increments)
const linear = generateSpacingScale({
  base: 4,
  method: "linear",
  steps: 8,
});
// Values: [0, 4, 8, 12, 16, 20, 24, 28]

// Exponential progression (dramatic hierarchy)
const exponential = generateSpacingScale({
  base: 4,
  method: "exponential",
  exponent: 2,
  steps: 6,
});
// Values: [0, 4, 8, 16, 32, 64]

// T-shirt sizes (semantic naming)
const tshirt = generateSpacingScale({
  base: 8,
  method: "t-shirt",
  steps: 8,
});
// Names: 3xs, 2xs, xs, sm, md, lg, xl, 2xl

// Presets
generateSpacingScale(SPACING_PRESETS.tailwind);   // 4px linear
generateSpacingScale(SPACING_PRESETS.material);   // 8px linear
generateSpacingScale(SPACING_PRESETS.natural);    // 8px fibonacci
generateSpacingScale(SPACING_PRESETS.semantic);   // 8px t-shirt

Shadow Generation

Generate layered shadows based on Material Design elevation.

import { generateShadows, SHADOW_PRESETS } from "bdsg";

const shadows = generateShadows({
  color: "#000000",
  baseOpacity: 0.1,
  levels: 6,
  style: "material",  // material | soft | hard | inset
  layered: true,
});

// Token structure
shadows.tokens[2];
// {
//   name: "md",
//   level: 2,
//   layers: [
//     { x: 0, y: 3, blur: 6, spread: 0, opacity: 0.12 },
//     { x: 0, y: 2, blur: 4, spread: -1, opacity: 0.1 },
//     { x: 0, y: 1, blur: 2, spread: 0, opacity: 0.06 }
//   ],
//   value: "0px 3px 6px 0px rgba(0,0,0,0.12), ..."
// }

// CSS variables output
shadows.cssVariables;
// "--shadow-none: none;
// --shadow-sm: 0px 1px 2px 0px rgba(0,0,0,0.08), ...
// --shadow-md: ..."

// Presets
generateShadows(SHADOW_PRESETS.material);     // 3-layer realistic
generateShadows(SHADOW_PRESETS.soft);         // High blur, single layer
generateShadows(SHADOW_PRESETS.brutalist);    // Hard edges, no blur
generateShadows(SHADOW_PRESETS.neumorphism);  // Inset + outer

Shadow styles:

  • material: Three layers (umbra, penumbra, ambient) for realistic depth
  • soft: Single layer with high blur for diffuse shadows
  • hard: Minimal blur, high contrast for retro look
  • inset: Inner shadows for pressed/recessed states

Relations Detection

Auto-detect relationships between design tokens using naming patterns.

import { detectRelations, wouldCreateDirectCycle } from "bdsg";

const existingNodes = [
  { id: "1", name: "primary", category: "color", type: "token", value: "#3B82F6" },
  { id: "2", name: "spacing-base", category: "spacing", type: "token", value: "8px" },
];

// Detect paired naming: "primary-text" uses "primary"
const newNode = { id: "3", name: "primary-text", category: "color", type: "token", value: "#FFF" };
detectRelations(newNode, existingNodes);
// [{
//   fromNodeId: "3",
//   toNodeId: "1",
//   type: "uses",
//   confidence: 0.9,
//   reason: 'Token "primary-text" uses "primary" (paired naming pattern)'
// }]

// Detect hierarchical: "spacing-sm" depends on "spacing-base"
detectRelations(
  { id: "4", name: "spacing-sm", category: "spacing", type: "token", value: "4px" },
  existingNodes
);
// [{ type: "depends_on", confidence: 0.7, ... }]

// Detect palette shades: "primary-500" uses "primary"
detectRelations(
  { id: "5", name: "primary-500", category: "color", type: "token", value: "#3B82F6" },
  existingNodes
);
// [{ type: "uses", confidence: 0.8, ... }]

// Prevent cycles
wouldCreateDirectCycle("node-1", "node-1");  // true
wouldCreateDirectCycle("node-1", "node-2");  // false

Detection patterns: | Pattern | Suffixes | Relation Type | Confidence | |---------|----------|---------------|------------| | Paired naming | -text, -bg, -border, -hover | uses | 0.9 | | Hierarchical | -base, -default, -primary | depends_on | 0.7 | | Palette shades | -50 to -900 | uses | 0.8 |

Validation

All generation functions validate input using Zod schemas. Invalid configurations throw descriptive errors:

generateTypographyScale({ base: 4 });
// Error: Invalid typography config: Number must be greater than or equal to 8

generateSpacingScale({ steps: 50 });
// Error: Invalid spacing config: Number must be less than or equal to 20

generatePalette("invalid");
// Error: Invalid base color: "invalid". Invalid hex color. Expected format: #RRGGBB or #RGB

License

MIT