Palette export
@slashclone/palette-export
Turn your design tokens into a downloadable color palette: a swatch sheet (SVG), JSON, CSS variables, and a GIMP palette that Krita and Inkscape also open. Handy for briefing designers or image models with your exact colors. Reads one colors file and writes only to an out folder.
- Activity
- New: be the first to try it
- Installs
- 0 installs
- Likes
- 0 likes
- Clones
- 0 clones
- 1#!/usr/bin/env node
- 2// Exports a color palette as JSON, CSS variables, a GIMP palette (.gpl), and an SVG swatch
- 3// sheet. Node built-ins only.
- 4// Usage: node scripts/export-palette.mjs --in data/colors.json [--name Brand] [--prefix brand]
- 5// [--out out]
- 6// Input: { "groups": [{ "name", "note"?, "colors": { name: "#RRGGBB" } }] } or a flat
- 7// { name: "#RRGGBB" } map.
- 8import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
- 9import { join } from "node:path";
- 10import { parseArgs } from "node:util";
- 12const { values } = parseArgs({
- 13 options: {
- 14 in: { type: "string" },
- 15 name: { type: "string", default: "Palette" },
- 16 prefix: { type: "string" },
- 17 out: { type: "string", default: "out" },
- 18 },
- 19});
- 20if (!values.in) {
- 21 console.error("Missing --in: a JSON file of colors.");
- 22 process.exit(2);
- 23}
- 25const raw = JSON.parse(readFileSync(values.in, "utf8"));
- 26const groups = Array.isArray(raw.groups)
- 27 ? raw.groups
- 28 : [{ name: values.name, note: "", colors: raw }];
- 29const HEX = /^#[0-9a-f]{6}$/i;
- 30for (const g of groups) {
- 31 for (const [key, hex] of Object.entries(g.colors ?? {})) {
- 32 if (typeof hex !== "string" || !HEX.test(hex)) {
- 33 console.error(`"${key}" in "${g.name}" is not a #RRGGBB color: ${String(hex)}`);
- 34 process.exit(1);
- 35 }
- 36 }
- 37}
- 39const slug = (s) =>
- 40 s
- 41 .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
- 42 .replace(/[^a-z0-9]+/gi, "-")
- 43 .replace(/^-|-$/g, "")
- 44 .toLowerCase();
- 45const prefix = slug(values.prefix ?? values.name);
- 46const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
- 47const all = groups.flatMap((g) => Object.entries(g.colors));
- 48mkdirSync(values.out, { recursive: true });
- 50writeFileSync(
- 51 join(values.out, "palette.json"),
- 52 JSON.stringify(
- 53 {
- 54 name: values.name,
- 55 groups: groups.map((g) => ({ name: g.name, note: g.note ?? "", colors: g.colors })),
- 56 },
- 57 null,
- 58 2,
- 59 ) + "\n",
- 60);
- 62writeFileSync(
- 63 join(values.out, "palette.css"),
- 64 `/* ${values.name} */\n:root {\n${all.map(([k, c]) => ` --${prefix}-${slug(k)}: ${c};`).join("\n")}\n}\n`,
- 65);
- 67writeFileSync(
- 68 join(values.out, "palette.gpl"),
- 69 `GIMP Palette\nName: ${values.name}\nColumns: 8\n#\n${all
- 70 .map(([k, c]) => {
- 71 const n = parseInt(c.slice(1), 16);
- 72 const ch = (s) => String((n >> s) & 255).padStart(3);
- 73 return `${ch(16)} ${ch(8)} ${ch(0)}\t${k}`;
- 74 })
- 75 .join("\n")}\n`,
- 76);
- 78// Swatch sheet: one row per group, the hex on each chip in a color that reads on it.
- 79const luminance = (c) => {
- 80 const n = parseInt(c.slice(1), 16);
- 81 return (0.299 * ((n >> 16) & 255) + 0.587 * ((n >> 8) & 255) + 0.114 * (n & 255)) / 255;
- 82};
- 83const W = 1600;
- 84const cols = 8;
- 85const sw = 172;
- 86const sh = 112;
- 87const gap = 16;
- 88let y = 96;
- 89const parts = [];
- 90for (const g of groups) {
- 91 parts.push(
- 92 `<text x="48" y="${y}" font-size="24" font-weight="700" fill="#222">${esc(g.name)}</text>`,
- 93 );
- 94 if (g.note) {
- 95 parts.push(`<text x="48" y="${y + 26}" font-size="16" fill="#666">${esc(g.note)}</text>`);
- 96 }
- 97 y += 48;
- 98 Object.entries(g.colors).forEach(([k, c], i) => {
- 99 const x = 48 + (i % cols) * (sw + gap);
- 100 const yy = y + Math.floor(i / cols) * (sh + 48);
- 101 const ink = luminance(c) > 0.55 ? "#222" : "#FFF";
- 102 parts.push(
- 103 `<rect x="${x}" y="${yy}" width="${sw}" height="${sh}" rx="14" fill="${c}" stroke="#0002"/>` +
- 104 `<text x="${x + 12}" y="${yy + sh - 14}" font-size="16" font-family="ui-monospace, Menlo, monospace" fill="${ink}">${c.toUpperCase()}</text>` +
- 105 `<text x="${x}" y="${yy + sh + 22}" font-size="15" fill="#222">${esc(k)}</text>`,
- 106 );
- 107 });
- 108 y += Math.ceil(Object.keys(g.colors).length / cols) * (sh + 48) + 24;
- 109}
- 110writeFileSync(
- 111 join(values.out, "palette.svg"),
- 112 `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${y}" viewBox="0 0 ${W} ${y}" font-family="system-ui, sans-serif">` +
- 113 `<rect width="100%" height="100%" fill="#FAFAF7"/>` +
- 114 `<text x="48" y="56" font-size="34" font-weight="800" fill="#222">${esc(values.name)}</text>` +
- 115 parts.join("") +
- 116 `</svg>\n`,
- 117);
- 119console.log(
- 120 `${String(all.length)} colors in ${String(groups.length)} groups -> ${values.out}/palette.{json,css,gpl,svg}`,
- 121);

