vanilla-colorful
A tiny, dependency-free color picker built as a native Custom Element that works with any framework.
Repository Health
Technical Analysis
vanilla-colorful is a framework-agnostic color picker distributed as a set of native Web Components, ported from react-colorful’s proven interaction model into standards-based Custom Elements. Instead of shipping one picker, it exposes fifteen separate entry points covering every common color format (HEX, HEX with alpha, HSL, HSLA, HSV, HSVA, RGB, RGBA, plus their string variants), so consumers import only the specific element they need rather than a bundled kitchen sink.
Each picker is built on a shared abstract ColorPicker base class that manages a shadow-DOM-rendered saturation/hue slider pair, syncing an internal HSVA representation with whatever public color format the concrete subclass exposes. Because it has zero runtime dependencies and compiles to a few kilobytes per entry point, it drops into React, Vue, Svelte, Angular, Lit, or plain HTML equally well, with CSS Shadow Parts exposed for style overrides and full keyboard and WAI-ARIA support built into the slider primitives.
What You Get
- Fifteen individually importable custom elements —
<hex-color-picker>,<hex-alpha-color-picker>,<hsl-color-picker>,<hsla-color-picker>,<hsv-color-picker>,<hsva-color-picker>,<rgb-color-picker>,<rgba-color-picker>, and their string-formatted counterparts — so you only ship the format you actually use - A
<hex-input>element for typed HEX entry, independent of the visual pickers, with optional alpha and#-prefix support - Exported base classes (e.g.
RgbBase) undervanilla-colorful/lib/entrypoints/*for defining your own custom-tag-named picker without re-registering the built-in element names - Full TypeScript definitions shipped in the package itself, including typed
color-changedCustomEvent payloads per color model - CSS Shadow Parts (
part(saturation),part(hue),part(hue-pointer), etc.) for overriding the picker’s visual styling without piercing the shadow root
Common Use Cases
- Adding a lightweight color picker to a design tool, theme editor, or CMS admin panel without pulling in a framework-specific dependency
- Embedding a color input inside a web component design system alongside other Custom Elements
- Building a brand/theme customizer where end users pick accent colors and the app needs the value in a specific format (HEX, RGB, HSL) for downstream styling
- Replacing a heavier React-only color picker library in a multi-framework codebase (Lit, Svelte, Vue) where a framework-agnostic element is required
Under The Hood
Architecture
Execution starts at a format-specific entry point (e.g. src/hex-color-picker.ts) which extends a per-model base class exported from src/lib/entrypoints/*.ts, which in turn extends the shared abstract ColorPicker<C> in src/lib/components/color-picker.ts. That base class owns the shadow root, renders a Saturation and Hue slider pair (src/lib/components/saturation.ts, hue.ts), and keeps an internal HSVA representation synchronized with the public color value through a per-model ColorModel object (toHsva/fromHsva/equal/fromAttr) supplied by the concrete subclass. Symbol-keyed private fields ($color, $hsva, $update, $isSame) keep internal state off the public API surface. Slider interaction (mouse, touch, keyboard) is centralized in the abstract Slider class, which fires a custom move event that the color picker’s shared handleEvent listener merges into the HSVA state — a clean separation between input handling and color-model conversion that would need to change in exactly one place (the base classes) if the picker’s rendering model changed.
Tech Stack
Written entirely in strict TypeScript, compiled with tsc and bundled per-entry-point with Rollup (@rollup/plugin-node-resolve, @rollup/plugin-terser) via a wireit-orchestrated task graph. Styles are authored as .css files under src/lib/styles/ and compiled to JS modules by a custom scripts/build-styles.cjs step before TypeScript compilation. Testing runs on @web/test-runner with Chai assertions and Sinon for spies, executed against real browsers rather than a DOM shim. @custom-elements-manifest/analyzer generates custom-elements.json and web-types metadata for IDE/editor tooling. No frontend framework is used or required at runtime — the only dependency surface is the browser’s native Custom Elements and Shadow DOM APIs.
Code Quality
The src/test/ directory covers unit-level color conversion utilities (utils.test.ts), integration-level picker behavior (color-picker.test.ts), the standalone hex input (hex-input.test.ts), and accessibility (a11y.test.ts), and the README advertises 100% coverage enforced via the test wireit task. ESLint with @typescript-eslint and Prettier are wired into lint-staged pre-commit hooks, and GitHub Actions runs the test suite on every push. Naming is consistent and small (single-purpose files under src/lib/utils/ for convert, compare, validate, math, dom), and TypeScript strict mode plus exported types (RgbColor, HsvaColor, etc.) give consumers compile-time safety on color shapes.
What Makes It Unique
Rather than shipping one configurable picker, the library exposes fifteen independent entry points so bundlers tree-shake away every color-format implementation a consumer doesn’t import — a deliberate size-first design also enforced at CI time via size-limit budgets per entry file (roughly 2.5-3.1 KB gzipped each). Porting react-colorful’s interaction logic to framework-free Custom Elements, while keeping typed events and exposing ::part() hooks for styling, is a comprehensive execution of “vanilla Web Components done properly” rather than a novel algorithmic contribution — its differentiator is packaging and portability, not a new picker interaction model.