Skip to main content
←Back to projects

CASE STUDY · 02

Craftie: your brand identity from a single image

Start with an inspiration: pick a curated style or upload an image and Craftie instantly loads your brand’s seed colors. Then adjust roles, check contrast in context, choose typography and export your guide.

ROLE
End-to-end design and development
TYPE
Web tool · Personal project
STACK
Next.js 16 · React 19 · TypeScript · culori (OKLCH) · Vitest
STATUS
Live · 2026
Craftie: your brand identity from

THE PROBLEM

Picking colors that look good together and are also readable is hard. Most tools give you pretty colors but never tell you whether text on top will be legible.

THE GOAL

Go from an image or an idea to a complete brand system (colors, contrast and typography) without needing to know color theory.

PROCESS

From a palette to a brand system

  1. 01

    Inspiration

    Pick a curated style or upload an image. Dominant colors are extracted right in your browser.

  2. 02

    Color roles

    Colors become roles (background, text, primary, accent) with light and dark versions.

  3. 03

    Legibility

    Every pairing is checked against WCAG 2.2, with a complementary APCA reading.

  4. 04

    Typography and export

    Curated pairs, Google Fonts or local fonts, and a guide you download in the format you need.

TECHNICAL DECISIONS

What happens behind the interface

All color logic lives in pure, tested functions, separate from the UI.

colorMath.tsOKLCH contrast
// lib/utils/colorMath.ts: adjust only lightness until contrast passes
export function adjustLightnessForContrast(colorHex: string, againstHex: string, targetRatio: number) {
  const normalized = normalizeHex(colorHex);
  if (contrastRatio(normalized, againstHex) >= targetRatio) return normalized;

  const { c, h, l: originalL = 0 } = parseOklch(normalized);
  const ratioAt = (lightness: number) => contrastRatio(oklchToHex(lightness, c, h), againstHex);

  const darker = findLightnessBound(ratioAt, targetRatio, 0, originalL, true);
  const lighter = findLightnessBound(ratioAt, targetRatio, originalL, 1, false);
  // ...pick the candidate closest to the original color
}
  • Why OKLCH?

    It changes lightness without shifting hue, so a color can be tuned to pass contrast while still looking like the original.

  • Suggestions, not just errors

    If a pairing fails AA or AAA, Craftie proposes the smallest lightness change that makes it pass.

  • Everything in your browser

    Images and fonts you upload never leave your computer: no server, no accounts.

  • 500+ tests

    The color and contrast engine is covered by Vitest tests, so results are predictable.

THE SOLUTION

How it works

Screenshot or video of start with an inspiration
01

Start with an inspiration

Pick a curated style or upload an image (JPG, PNG or WebP, up to 5 MB). Craftie extracts the colors and classifies them as pastel, vivid, dark or neutral.

Screenshot or video of tap a role and adjust it
02

Tap a role and adjust it

Change the color of any role and check contrast in context: WCAG AA or AAA, plus an APCA reading.

Screenshot or video of export your guide
03

Export your guide

Choose typography (curated pairs, Google Fonts or your own fonts) and export to CSS, design tokens (JSON), Figma, DESIGN.md or brand kit.

RESULTS

By the numbers

500+

Automated tests

Color, contrast and export logic covered with Vitest.

~90

Font pairings

Recommended based on the style you choose.

5

Export formats

CSS, JSON, Figma Tokens, DESIGN.md and brand kit.

WHAT I LEARNED

An accessibility tool has to be accessible first.
Technical takeaway:

Keeping color logic in pure functions, away from the UI, let me redesign the interface many times without breaking the math.

Product takeaway:

Showing the contrast ratio next to a concrete fix helps far more than a plain "fails".

Tell me about your idea

It doesn't need to be fully formed, and you don't need to know what it's called. Write to me like you'd write to a friend and I'll reply within 24 hours.

Replies within 24 hours