This is the API documentation for QuarkSuite Core. It’s a technical overview of everything you can do with the library. For general use, you’ll want to read the handbook instead. The utilities are organized based on the module they’re associated with.
This is a very straightforward document, but if something is unclear, please open an issue and let me know.
QuarkSuite Core has certain idioms based around its design, and it helps to be aware of them while reading.
When we talk about types in QuarkSuite, we mean input and output types. This is signficant because it lets you know what kind of data a utility takes and gives back. The input types as of this writing are:
color
: any valid CSS color stringpalette
: generated palette tokensroot
: any CSS value (unitless allowed)dict
: a wrapped token dictionary with a requiredproject
property for exporting
Any type that is not explicitly mentioned here is local to the given utility.
Tokens in QuarkSuite are assembled data collections. If you’re familiar with how they worked in v1, you should know that it’s changed significantly. Instead of manual assembly, token output now relies on assembling generated data into conventional configurations.
This reduces a lot of the object creation boilerplate that v1 attempted to mitigate by providing the formula and bootstrapper layers. Both are completely unnecessary in v2.
Dictionaries in QuarkSuite are packaged token collections. They’re identified by a project
property containing an
object. Exporting functions require it (even if it’s empty) before they will prepare tokens for their target formats.
Dictionaries are hierarchical and structured the way you define them.
You can optionally define metadata at any group level or in the project settings themselves, but take care as some exporters will ignore it depending on the format you use.
The included utilities are designed around a data-last architecture. Every included function (outside of the
workflow.js
module) expects one of two signatures:
action(y, x)
: a function that accepts typex
and ay
modifier to configure its outputexporter(format, dict)
: a special action that accepts typedict
and transforms its output based on a targetformat
Emitters are a third type of signature that only come into play with advanced use through the workflow.js
module:
emitter(x)
: a preset action waiting for the relevant type ofx
Converting actions into emitters is the driving force of advanced QuarkSuite workflows.
The shared rule between all exporters (with the exception of those for interop because they translate schemas rather than export): assume no access to the user’s filesystem. The output of exporters is file-ready for the target formats, but you’ll have to take it the rest of the way with your JS environment’s native filesystem API or a library of your choice.
For security reasons, QuarkSuite does not generate output files.
The color.js
module contains all functionality related to creating and manipulating color, generating palettes, and
assembling color dictionaries.
You should be aware that all color processing happens in the Oklab color space. Using a perceptually uniform space like Oklab was key in making color data composition and configurations both possible and predictable.
That said, every color output is anchored in sRGB as that is the expected color space of the web.
An action that takes any valid CSS color
and converts it to
a given target format.
to: hex | rgb | hsl | cmyk | hwb | lab | lch | oklab | oklch
: the target formatcolor: string
: the input color
string
: the converted color
import { convert } from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
const color = "rebeccapurple";
// Highly recommend converting CSS Color Module 4/5 formats to hex, rgb, hsl
console.log(convert("hex", color)); // #663399
console.log(convert("rgb", color)); // rgb(102, 51, 153)
console.log(convert("hsl", color)); // hsl(270, 50%, 40%)
// When browsers are ready, you can use these
console.log(convert("cmyk", color)); // device-cmyk(33.333% 66.667% 0% 40%)
console.log(convert("hwb", color)); // hwb(270 20% 40%)
console.log(convert("lab", color)); // lab(32.393% 38.428 -47.69)
console.log(convert("lch", color)); // lch(32.393% 61.246 308.862)
console.log(convert("oklab", color)); // oklab(44.027% 0.08818 -0.13386)
console.log(convert("oklch", color)); // oklch(44.027% 0.1603 303.373)
Every color function in the library outputs colors in the format of the input color. The exception are named colors, which will always be converted to hexadecimal if used as input.
An action that takes any valid CSS color
and adjusts its properties according to user settings
.
settings: {}
: color adjustment settingssettings.lightness = 0: number
: adjust the lightness of a color (as a percentage)settings.chroma = 0: number
: adjust the chroma/intensity of a color (as a percentage)settings.hue = 0: number
: adjust the hue of a color (in degrees)settings.alpha = 0: number
: adjust the alpha/transparency of a color (as a percentage)settings.steps = 0: number
: interpolates the color adjustment (up to number of steps)
color: string
: the input color
string | string[]
: the adjusted color or interpolation data
import { convert, adjust } from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
const color = convert("rgb", "rebeccapurple");
// You can adjust a single property
console.log(adjust({ lightness: 20 }, color)); // rgb(161, 112, 219)
console.log(adjust({ chroma: 50 }, color)); // rgb(119, 0, 206)
console.log(adjust({ hue: 120 }, color)); // rgb(140, 52, 0)
// Negative values mean a decrease
console.log(adjust({ lightness: -20 }, color)); // rgb(49, 0, 91)
console.log(adjust({ chroma: -50 }, color)); // rgb(86, 78, 99)
console.log(adjust({ hue: -120 }, color)); // rgb(0, 108, 92)
console.log(adjust({ alpha: -30 }, color)); // rgba(102, 51, 153, 0.7)
// You can adjust multiple properties
console.log(adjust({ lightness: 10, chroma: -20, hue: 90 }, color)); // rgb(165, 84, 67)
console.log(adjust({ chroma: 15, hue: -60 }, color)); // rgb(0, 83, 181)
// Interpolation activated
console.log(adjust({ lightness: 10, chroma: -50, hue: 360, steps: 5 }, color));
// [
// "rgb(148, 47, 62)",
// "rgb(120, 88, 0)",
// "rgb(49, 114, 82)",
// "rgb(67, 111, 134)",
// "rgb(115, 106, 128)"
// ]
- Percentage values lock at
±0-100
- Hue locks at
±0-360
An action that takes any valid CSS color
and mixes it according to user settings
.
settings: {}
: color blending settingssettings.target = color: string
: set the blend targetsettings.strength = 0: number
: set the blend strength (as a percentage)settings.steps = 0: number
: interpolates the color blending (up to number of steps)
color: string
: the input color
string | string[]
: the blended color or interpolation data
import { convert, mix } from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
const color = convert("hsl", "rebeccapurple");
const target = "crimson";
// Blending toward the target color
console.log(mix({ target, strength: 0 }, color)); // hsl(270, 50, 40%)
console.log(mix({ target, strength: 25 }, color)); // hsl(296.154, 40.625%, 37.647%)
console.log(mix({ target, strength: 50 }, color)); // hsl(326.538, 48.148%, 42.353%)
console.log(mix({ target, strength: 75 }, color)); // hsl(341.538, 60.338%, 46.471%)
console.log(mix({ target, strength: 100 }, color)); // hsl(348, 83.333%, 47.059%)
// Blending from the target color
console.log(mix({ target, strength: -0 }, color)); // hsl(348, 83.333%, 47.059%)
console.log(mix({ target, strength: -25 }, color)); // hsl(341.538, 60.338%, 46.471%)
console.log(mix({ target, strength: -50 }, color)); // hsl(326.538, 48.148%, 42.353%)
console.log(mix({ target, strength: -75 }, color)); // hsl(296.154, 40.625%, 37.647%)
console.log(mix({ target, strength: -100 }, color)); // hsl(270, 50, 40%)
// Interpolation activated
console.log(mix({ target, strength: 100, steps: 5 }, color));
// [
// "hsl(290.488, 42.268%, 38.039%)",
// "hsl(316.484, 44.39%, 40.196%)",
// "hsl(333.782, 52.889%, 44.118%)",
// "hsl(343.421, 63.333%, 47.059%)",
// "hsl(348, 83.333%, 47.059%)"
// ]
- Percentage values lock at
±0-100
An action that takes any valid CSS color
and a generates an artistic color harmony according to user settings
.
settings: {}
: color harmony settingssettings.configuration = complementary: dyadic | complementary | analogous | split | triadic | clash | double | tetradic | square
: set the color harmony configurationsettings.accented = false: boolean
: accented variant? (withdyadic
,analogous
,split
,triadic
)
color: string
: the input color
string[]
: the generated color harmony
import { convert, harmony } from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
const color = convert("hex", "rebeccapurple");
// Rotational harmonies
console.log(harmony({ configuration: "dyadic" }, color)); // ["#663399", "#832477"]
console.log(harmony({ configuration: "analogous" }, color)); // ["#663399", "#832477", "#931849"]
console.log(harmony({ configuration: "complementary" }, color)); // ["#663399", "#425e00"]
// Triadic harmonies
console.log(harmony({ configuration: "split" }, color)); // ["#663399", "#714c00", "#006921"]
console.log(harmony({ configuration: "triadic" }, color)); // ["#663399", "#8c3400", "#006c5c"]
console.log(harmony({ configuration: "clash" }, color)); // ["#663399", "#971e01", "#006587"]
// Tetradic harmonies
console.log(harmony({ configuration: "double" }, color)); // ["#663399", "#832477", "#425e00", "#006921"]
console.log(harmony({ configuration: "tetradic" }, color)); // ["#663399", "#931849", "#425e00", "#006c5c"]
console.log(harmony({ configuration: "square" }, color)); // ["#663399", "#971e01", "#425e00", "#006587"]
// Accented harmonies
console.log(harmony({ configuration: "dyadic", accented: true }, color)); // ["#663399", "#832477", "#425e00"]
console.log(harmony({ configuration: "analogous", accented: true }, color)); // ["#663399", "#832477", "#931849", "#425e00"]
console.log(harmony({ configuration: "split", accented: true }, color)); // ["#663399", "#714c00", "#425e00", "#006921"]
console.log(harmony({ configuration: "triadic", accented: true }, color)); // [ "#663399", "#8c3400", "#425e00", "#006c5c" ]
An action that takes any valid CSS color
and generates a palette according to user settings
.
settings: {}
: palette settingssettings.configuration = material: material | artistic
: set the palette configurationsettings.contrast = 100: number
: set the overall palette contrast (both configurations)settings.accents = false: boolean
include accent colors? (both configurations)settings.dark = false: boolean
: toggle dark mode? (both configurations)settings.states = false: boolean
: include interface states? (material
)settings.tints = 3: number
: number of tints to generate (artistic
)settings.tones = 3: number
: number of tones to generate (artistic
)settings.shades = 3: number
: number of shades to generate (artistic
)
color: string
: the input color
{}
: the generated palette tokens
import { convert, palette } from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
const color = convert("hex", "rebeccapurple");
// Material configuration:
console.log(palette({ configuration: "material" }, color));
// {
// 50: "#eeeaf6",
// 100: "#d1c5e4",
// 200: "#b5a1d2",
// 300: "#9a7dc0",
// 400: "#7f59ad",
// 500: "#552e7e",
// 600: "#452964",
// 700: "#35234b",
// 800: "#261c34",
// 900: "#18151d",
// bg: "#ffffff",
// fg: "#111111"
// }
// Material contrast adjustment
console.log(palette({ configuration: "material", contrast: 80 }, color));
// {
// 50: "#baa8d6",
// 100: "#a991ca",
// 200: "#977abe",
// 300: "#8663b2",
// 400: "#764ca6",
// 500: "#5b3088",
// 600: "#512d77",
// 700: "#462966",
// 800: "#3c2556",
// 900: "#322146",
// bg: "#ded5ec",
// fg: "#201929"
// }
// Material with accents
console.log(palette({ configuration: "material", accents: true }, color));
// {
// 50: "#eeeaf6",
// 100: "#d1c5e4",
// 200: "#b5a1d2",
// 300: "#9a7dc0",
// 400: "#7f59ad",
// 500: "#552e7e",
// 600: "#452964",
// 700: "#35234b",
// 800: "#261c34",
// 900: "#18151d",
// bg: "#ffffff",
// fg: "#111111",
// a50: "#6dbfb3",
// a100: "#3facba",
// a200: "#2993c0",
// a300: "#3c76c0",
// a400: "#5556b4",
// a500: "#6e0070",
// a600: "#6d003d",
// a700: "#650000",
// a800: "#530000",
// a900: "#370000"
// }
// Material with interface states
console.log(palette({ configuration: "material", states: true }, color));
// {
// 50: "#eeeaf6",
// 100: "#d1c5e4",
// 200: "#b5a1d2",
// 300: "#9a7dc0",
// 400: "#7f59ad",
// 500: "#552e7e",
// 600: "#452964",
// 700: "#35234b",
// 800: "#261c34",
// 900: "#18151d",
// bg: "#ffffff",
// fg: "#111111",
// state: { pending: "#75707c", success: "#366b41", warning: "#83713f", error: "#86343a" }
// }
// Material dark mode
console.log(palette({
configuration: "material",
dark: true,
accents: true,
states: true
}, color));
// {
// 50: "#18151d",
// 100: "#261c34",
// 200: "#35234b",
// 300: "#452964",
// 400: "#552e7e",
// 500: "#7f59ad",
// 600: "#9a7dc0",
// 700: "#b5a1d2",
// 800: "#d1c5e4",
// 900: "#eeeaf6",
// bg: "#111111",
// fg: "#ffffff",
// a50: "#00100b",
// a100: "#001d2b",
// a200: "#00284f",
// a300: "#002e71",
// a400: "#36318c",
// a500: "#953496",
// a600: "#c23582",
// a700: "#e93e5d",
// a800: "#ff5700",
// a900: "#ff7f00",
// state: { pending: "#dbd6e3", success: "#99d0a1", warning: "#ebd7a2", error: "#f39698" }
// }
import { convert, palette } from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
const color = convert("hex", "rebeccapurple");
// Artistic configuration:
console.log(palette({ configuration: "artistic" }, color));
// {
// bg: "#ffffff",
// fg: "#111111",
// light: { 100: "#9171ba", 200: "#beadd8", 300: "#eeeaf6" },
// muted: { 100: "#795aa0", 200: "#8e7da6", 300: "#a39fa9" },
// dark: { 100: "#4a2a6d", 200: "#302143", 300: "#18151d" }
// }
// Artistic contrast adjustment
console.log(palette({ configuration: "artistic", contrast: 80 }, color));
// {
// bg: "#ded5ec",
// fg: "#201929",
// light: { 100: "#8865b3", 200: "#ac95cc", 300: "#d1c5e4" },
// muted: { 100: "#75539f", 200: "#856fa4", 300: "#968aa8" },
// dark: { 100: "#502c75", 200: "#3a2553", 300: "#261c34" }
// }
// Artistic with adjusted variants
console.log(palette({
configuration: "artistic",
tints: 6,
tones: 2,
shades: 4
}, color));
// {
// bg: "#ffffff",
// fg: "#111111",
// light: {
// 100: "#7b53aa",
// 200: "#9171ba",
// 300: "#a78fc9",
// 400: "#beadd8",
// 500: "#d6cbe7",
// 600: "#eeeaf6"
// },
// muted: { 100: "#836ba3", 200: "#a39fa9" },
// dark: { 100: "#512d78", 200: "#3d2658", 300: "#2a1e39", 400: "#18151d" }
// }
// Artistic with omitted variants
console.log(palette({ configuration: "artistic", tints: 6, tones: 0, shades: 4 }, color));
// {
// bg: "#ffffff",
// fg: "#111111",
// light: {
// 100: "#7b53aa",
// 200: "#9171ba",
// 300: "#a78fc9",
// 400: "#beadd8",
// 500: "#d6cbe7",
// 600: "#eeeaf6"
// },
// dark: { 100: "#512d78", 200: "#3d2658", 300: "#2a1e39", 400: "#18151d" }
// }
// Artistic with accents
console.log(palette({ configuration: "artistic", accents: true }, color));
// {
// bg: "#ffffff",
// fg: "#111111",
// light: { 100: "#9171ba", 200: "#beadd8", 300: "#eeeaf6" },
// muted: { 100: "#795aa0", 200: "#8e7da6", 300: "#a39fa9" },
// dark: { 100: "#4a2a6d", 200: "#302143", 300: "#18151d" },
// accent: {
// 100: "#6dbfb3",
// 200: "#3facba",
// 300: "#2993c0",
// 400: "#3c76c0",
// 500: "#5556b4",
// 600: "#6f0064",
// 700: "#6a0020",
// 800: "#580000",
// 900: "#370000"
// }
// }
// Artistic dark mode
console.log(palette({
configuration: "artistic",
accents: true,
dark: true,
}, color));
// {
// bg: "#111111",
// fg: "#ffffff",
// light: { 100: "#9171ba", 200: "#beadd8", 300: "#eeeaf6" },
// muted: { 100: "#795aa0", 200: "#8e7da6", 300: "#a39fa9" },
// dark: { 100: "#4a2a6d", 200: "#302143", 300: "#18151d" },
// accent: {
// "100": "#00100b",
// "200": "#001d2b",
// "300": "#00284f",
// "400": "#002e71",
// "500": "#36318c",
// "600": "#a13493",
// "700": "#d73772",
// "800": "#ff4f2a",
// "900": "#ff7f00"
// }
// }
Palette generation in QuarkSuite Core v2 is organized around systems thinking. For example: the bg
and fg
colors are
meant to be local to the elements they apply to. That may or may not be the HTML body.
The material
configuration is especially suited for apps. If you include accents and interface states, you likely
won’t need to generate any more colors for your app even after filtering for accessibility.
The artistic
configuration is ideal for graphic design, creative coding projects, and content websites.
An action that takes a generated palette
and filters it for accessibility according to user settings
.
settings
: color accessibility filter settingssettings.mode: standard | custom
: set color accessibility modesettings.rating = AA: AA | AAA
: set color contrast rating (standard
)settings.large = false: boolean
: use large text rating? (standard
)settings.min = 85: number
: set minimum contrast from background (as a percentage)settings.max: number
: set maximum contrast from background (as a percentage)
palette: {}
: generated palette
{}
: the filtered palette
import {
convert,
palette,
a11y
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
const color = convert("hex", "rebeccapurple");
const material = palette({ configuration: "material", accents: true }, color);
const materialDark = palette({
configuration: "material",
accents: true,
dark: true
}, color);
// AA
console.log(a11y({ mode: "standard", rating: "AA" }, material));
// {
// 50: "#7f59ad",
// 100: "#552e7e",
// 200: "#452964",
// 300: "#35234b",
// 400: "#261c34",
// 500: "#18151d",
// bg: "#ffffff",
// fg: "#111111",
// a50: "#3c76c0",
// a100: "#5556b4",
// a200: "#6e0070",
// a300: "#6d003d",
// a400: "#650000",
// a500: "#530000",
// a600: "#370000"
// }
// AA (dark)
console.log(a11y({ mode: "standard", rating: "AA" }, materialDark));
// {
// 50: "#9a7dc0",
// 100: "#b5a1d2",
// 200: "#d1c5e4",
// 300: "#eeeaf6",
// bg: "#111111",
// fg: "#ffffff",
// a50: "#e93e5d",
// a100: "#ff5700",
// a200: "#ff7f00"
// }
// AA large
console.log(a11y({ mode: "standard", rating: "AA", large: true }, material));
// {
// 50: "#9a7dc0",
// 100: "#7f59ad",
// 200: "#552e7e",
// 300: "#452964",
// 400: "#35234b",
// 500: "#261c34",
// 600: "#18151d",
// bg: "#ffffff",
// fg: "#111111",
// a50: "#2993c0",
// a100: "#3c76c0",
// a200: "#5556b4",
// a300: "#6e0070",
// a400: "#6d003d",
// a500: "#650000",
// a600: "#530000",
// a700: "#370000"
// }
// AA large (dark)
console.log(a11y({ mode: "standard", rating: "AA", large: true }, materialDark));
// {
// 50: "#7f59ad",
// 100: "#9a7dc0",
// 200: "#b5a1d2",
// 300: "#d1c5e4",
// 400: "#eeeaf6",
// bg: "#111111",
// fg: "#ffffff",
// a50: "#c23582",
// a100: "#e93e5d",
// a200: "#ff5700",
// a300: "#ff7f00"
// }
// AAA
console.log(a11y({ mode: "standard", rating: "AAA" }, material));
// {
// 50: "#552e7e",
// 100: "#452964",
// 200: "#35234b",
// 300: "#261c34",
// 400: "#18151d",
// bg: "#ffffff",
// fg: "#111111",
// a50: "#6e0070",
// a100: "#6d003d",
// a200: "#650000",
// a300: "#530000",
// a400: "#370000"
// }
// AAA (dark)
console.log(a11y({ mode: "standard", rating: "AAA" }, materialDark));
// {
// 50: "#b5a1d2",
// 100: "#d1c5e4",
// 200: "#eeeaf6",
// bg: "#111111",
// fg: "#ffffff",
// a50: "#ff7f00"
// }
import {
convert,
palette,
a11y
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
const color = convert("hex", "rebeccapurple");
const material = palette({ configuration: "material", accents: true }, color);
const materialDark = palette({
configuration: "material",
accents: true,
dark: true
}, color);
// Custom: from 64%
console.log(a11y({ mode: "custom", min: 64 }, material));
// {
// 50: "#452964",
// 100: "#35234b",
// 200: "#261c34",
// 300: "#18151d",
// bg: "#ffffff",
// fg: "#111111",
// a50: "#6d003d",
// a100: "#650000",
// a200: "#530000",
// a300: "#370000"
// }
// Custom: from 45% (dark)
console.log(a11y({ mode: "custom", min: 45 }, materialDark));
// {
// 50: "#9a7dc0",
// 100: "#b5a1d2",
// 200: "#d1c5e4",
// 300: "#eeeaf6",
// bg: "#111111",
// fg: "#ffffff",
// a50: "#ff5700",
// a100: "#ff7f00"
// }
// Custom: from 64% to 70%
console.log(a11y({ mode: "custom", min: 64, max: 70 }, material));
// { "50": "#452964", bg: "#ffffff", fg: "#111111", a50: "#6d003d", a100: "#650000" }
// Custom: from 45% to 70% (dark)
console.log(a11y({ mode: "custom", min: 45, max: 70 }, materialDark));
// { "50": "#9a7dc0", bg: "#111111", fg: "#ffffff" }
You’ll prefer the standard
accessibility mode in the majority of cases as it enforces WCAG compliance. The custom
mode will be more valuable for colorimetrically contrasted palettes under rare circumstances.
It’s also important to know that the a11y()
action isn’t a destructive update. The input palette is left untouched so
you’re able to compare the results during development.
An action that takes a generated palette
and overlays a color perception simulator according to user settings
.
settings: {}
: color perception simulation settingssettings.check: vision | contrast | illuminant
: set simulation targetsettings.severity = 50: number
: set severity of simulation (where applicable)settings.as = protanopia: achromatopsia | protanomaly | protanopia | deuteranomaly | deuteranopia | tritanomaly | tritanopia
: set colorblindness to targetsettings.method = brettel: brettel | vienot
: set colorblindness algorithm to usesettings.factor = 0: number
: set contrast sensitivity gray factor (as a percentage)settings.K = 1850: number
: set illuminant temperature (in kelvins)
palette: {}
: generated palette
{}
: the simulated palette
import {
convert,
palette,
perception
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
const color = convert("hex", "rebeccapurple");
const material = palette({ configuration: "material", accents: true }, color);
// Achromatopsia
console.log(perception({ check: "vision", as: "achromatopsia" }, material));
// {
// 50: "#ececec",
// 100: "#cbcbcb",
// 200: "#acacac",
// 300: "#8d8d8d",
// 400: "#6f6f6f",
// 500: "#464646",
// 600: "#393939",
// 700: "#2d2d2d",
// 800: "#222222",
// 900: "#171717",
// bg: "#ffffff",
// fg: "#111111",
// a50: "#adadad",
// a100: "#9b9b9b",
// a200: "#878787",
// a300: "#757575",
// a400: "#646464",
// a500: "#424242",
// a600: "#3a3a3a",
// a700: "#323232",
// a800: "#282828",
// a900: "#191919"
// }
// Protanopia
console.log(perception({ check: "vision", as: "protanopia" }, material));
// {
// 50: "#e7ebf6",
// 100: "#bcc8e4",
// 200: "#91a6d2",
// 300: "#6284c0",
// 400: "#2563ad",
// 500: "#00397e",
// 600: "#003164",
// 700: "#0a284b",
// 800: "#111e34",
// 900: "#12161d",
// bg: "#ffffff",
// fg: "#111111",
// a50: "#bab8b3",
// a100: "#9da5ba",
// a200: "#768ec0",
// a300: "#4276c0",
// a400: "#005cb4",
// a500: "#002a70",
// a600: "#16253d",
// a700: "#261f02",
// a800: "#1e1801",
// a900: "#110d00"
// }
// Protanopia (vienot)
console.log(perception({
check: "vision",
as: "protanopia",
method: "vienot"
}, material));
// {
// 50: "#eaeaf6",
// 100: "#c6c6e4",
// 200: "#a3a3d2",
// 300: "#8181c0",
// 400: "#5e5ead",
// 500: "#34347e",
// 600: "#2d2d64",
// 700: "#25254b",
// 800: "#1d1d34",
// 900: "#15151d",
// bg: "#ffffff",
// fg: "#111111",
// a50: "#b8b8b3",
// a100: "#a4a4ba",
// a200: "#8c8cc0",
// a300: "#7171c0",
// a400: "#5656b4",
// a500: "#242470",
// a600: "#23233d",
// a700: "#202002",
// a800: "#191901",
// a900: "#0e0e01"
// }
// Protanomaly (32%)
console.log(perception({
check: "vision",
as: "protanomaly",
severity: 32
}, material));
// {
// 50: "#eceaf6",
// 100: "#cbc6e4",
// 200: "#aba2d2",
// 300: "#8b7fc0",
// 400: "#6c5cad",
// 500: "#43327e",
// 600: "#382c64",
// 700: "#2c254b",
// 800: "#211d34",
// 900: "#16151d",
// bg: "#ffffff",
// fg: "#111111",
// a50: "#8cbdb3",
// a100: "#69aaba",
// a200: "#4c92c0",
// a300: "#3e76c0",
// a400: "#4358b4",
// a500: "#581570",
// a600: "#5c123d",
// a700: "#570e01",
// a800: "#470a00",
// a900: "#2e0400"
// }
// Deuteranopia
console.log(perception({ check: "vision", as: "deuteranopia" }, material));
// {
// 50: "#e9ecf6",
// 100: "#c2cbe4",
// 200: "#9bacd1",
// 300: "#748dbf",
// 400: "#4a6fac",
// 500: "#18467d",
// 600: "#1c3a63",
// 700: "#1c2e4b",
// 800: "#182234",
// 900: "#14171d",
// bg: "#ffffff",
// fg: "#111111",
// a50: "#aaadb4",
// a100: "#8c9abb",
// a200: "#6887c1",
// a300: "#4175c0",
// a400: "#1864b4",
// a500: "#27436f",
// a600: "#3c3c3b",
// a700: "#3e3400",
// a800: "#322900",
// a900: "#201900"
// }
// Deuteranopia (vienot)
console.log(perception({
check: "vision",
as: "deuteranopia",
method: "vienot"
}, material));
// {
// 50: "#ebebf6",
// 100: "#c9c9e4",
// 200: "#a7a7d2",
// 300: "#8686c0",
// 400: "#6666ad",
// 500: "#3c3c7e",
// 600: "#333364",
// 700: "#29294b",
// 800: "#1f1f34",
// 900: "#16161d",
// bg: "#ffffff",
// fg: "#111111",
// a50: "#acacb4",
// a100: "#9696bb",
// a200: "#7f7fc1",
// a300: "#6969c0",
// a400: "#5656b4",
// a500: "#3c3c6f",
// a600: "#3c3c3b",
// a700: "#373700",
// a800: "#2c2c00",
// a900: "#1b1b00"
// }
// Deuteranomaly (48%)
console.log(perception({
check: "vision",
as: "deuteranomaly",
severity: 48
}, material));
// {
// 50: "#ebebf6",
// 100: "#cac8e4",
// 200: "#a9a6d2",
// 300: "#8985c0",
// 400: "#6a64ad",
// 500: "#403b7e",
// 600: "#363264",
// 700: "#2b284b",
// 800: "#201f34",
// 900: "#16161d",
// bg: "#ffffff",
// fg: "#111111",
// a50: "#8fb7b4",
// a100: "#6da4bb",
// a200: "#4f8ec0",
// a300: "#3e76c0",
// a400: "#405db4",
// a500: "#552e6f",
// a600: "#5a283c",
// a700: "#552200",
// a800: "#461b00",
// a900: "#2d0f00"
// }
// Tritanopia
console.log(perception({ check: "vision", as: "tritanopia" }, material));
// {
// 50: "#ececec",
// 100: "#cccaca",
// 200: "#aea9a9",
// 300: "#90898a",
// 400: "#716a6b",
// 500: "#484142",
// 600: "#3b3637",
// 700: "#2f2b2b",
// 800: "#222021",
// 900: "#171616",
// bg: "#ffffff",
// fg: "#111111",
// a50: "#77bad2",
// a100: "#47aac7",
// a200: "#1996b3",
// a300: "#198099",
// a400: "#3c6877",
// a500: "#662831",
// a600: "#6b0f23",
// a700: "#660019",
// a800: "#540013",
// a900: "#37000a"
// }
// Tritanomaly (64%)
console.log(perception({
check: "vision",
as: "tritanomaly",
severity: 64
}, material));
// {
// 50: "#edebf0",
// 100: "#cec8d4",
// 200: "#b0a6b9",
// 300: "#9385a0",
// 400: "#776487",
// 500: "#4d3b5d",
// 600: "#3f324b",
// 700: "#312839",
// 800: "#241f29",
// 900: "#171619",
// bg: "#ffffff",
// fg: "#111111",
// a50: "#74bcc7",
// a100: "#44aac2",
// a200: "#1f95b8",
// a300: "#2a7ca8",
// a400: "#466291",
// a500: "#691f4f",
// a600: "#6c0a2e",
// a700: "#650013",
// a800: "#53000d",
// a900: "#370006"
// }
import {
convert,
palette,
perception
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
const color = convert("hex", "rebeccapurple");
const material = palette({ configuration: "material", accents: true }, color);
// Black (55%)
console.log(perception({
check: "contrast",
factor: 0,
severity: 55
}, material));
// {
// 50: "#4f4d52",
// 100: "#44404b",
// 200: "#3a3245",
// 300: "#30253e",
// 400: "#261837",
// 500: "#160826",
// 600: "#11071c",
// 700: "#0b0513",
// 800: "#06030a",
// 900: "#030204",
// bg: "#555555",
// fg: "#020202",
// a50: "#1f3e39",
// a100: "#0e373c",
// a200: "#072d3e",
// a300: "#0d233e",
// a400: "#161739",
// a500: "#200020",
// a600: "#1f000e",
// a700: "#1c0000",
// a800: "#160000",
// a900: "#0b0000"
// }
// Gray (25%)
console.log(perception({
check: "contrast",
factor: 50,
severity: 25
}, material));
// {
// 50: "#c9c6cf",
// 100: "#b4abc2",
// 200: "#a091b5",
// 300: "#8c77a8",
// 400: "#785d9b",
// 500: "#583e78",
// 600: "#4c3965",
// 700: "#403352",
// 800: "#342d40",
// 900: "#29272d",
// bg: "#d6d6d6",
// fg: "#232323",
// a50: "#6da79e",
// a100: "#5199a4",
// a200: "#4387a8",
// a300: "#4972a9",
// a400: "#575ba0",
// a500: "#6e2a6e",
// a600: "#6e2646",
// a700: "#68251e",
// a800: "#5a211b",
// a900: "#451b16"
// }
// White (38%)
console.log(perception({
check: "contrast",
factor: 100,
severity: 38
}, material));
// {
// 50: "#f4f2f9",
// 100: "#e2dbee",
// 200: "#d0c4e4",
// 300: "#bfadd9",
// 400: "#ad97ce",
// 500: "#917bb0",
// 600: "#86769e",
// 700: "#7b708c",
// 800: "#706a7b",
// 900: "#66646a",
// bg: "#ffffff",
// fg: "#606060",
// a50: "#a7d7cf",
// a100: "#91ccd4",
// a200: "#85bcd9",
// a300: "#86aada",
// a400: "#9096d3",
// a500: "#a76ca6",
// a600: "#a96881",
// a700: "#a4675d",
// a800: "#97625a",
// a900: "#835b54"
// }
import {
convert,
palette,
perception
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
const color = convert("hex", "rebeccapurple");
const material = palette({ configuration: "material", accents: true }, color);
// Incandescent bulb (2400K)
console.log(perception({ check: "illuminant", K: 2400 }, material));
// {
// 50: "#f9c5a3",
// 100: "#eab39c",
// 200: "#daa195",
// 300: "#cb908e",
// 400: "#bc7e87",
// 500: "#a46871",
// 600: "#9b6463",
// 700: "#935f54",
// 800: "#8a5946",
// 900: "#825435",
// bg: "#ffcfa6",
// fg: "#7e512c",
// a50: "#c2b182",
// a100: "#b1a988",
// a200: "#a79d8f",
// a300: "#a49091",
// a400: "#a77f8d",
// a500: "#b35a69",
// a600: "#b45447",
// a700: "#b0511b",
// a800: "#a64e1a",
// a900: "#964918"
// }
// Illuminant: Studio lamp (3200K)
console.log(perception({ check: "illuminant", K: 3200 }, material));
// {
// 50: "#f8d2bb",
// 100: "#e9c0b3",
// 200: "#daaeac",
// 300: "#cb9ca4",
// 400: "#bb8b9d",
// 500: "#a47586",
// 600: "#9b7078",
// 700: "#926b69",
// 800: "#8a665b",
// 900: "#82614c",
// bg: "#ffdcbf",
// fg: "#7d5e44",
// a50: "#bfbe9a",
// a100: "#aeb59f",
// a200: "#a4aaa5",
// a300: "#a19ba7",
// a400: "#a68ba2",
// a500: "#b4687e",
// a600: "#b5625e",
// a700: "#b2603c",
// a800: "#a75d3a",
// a900: "#975736"
// }
// Illuminant: LCD Screen (6500-7800K)
console.log(perception({ check: "illuminant", K: 7200 }, material));
// {
// 50: "#ededfa",
// 100: "#dfdaf1",
// 200: "#d1c8e9",
// 300: "#c3b5e0",
// 400: "#b4a3d7",
// 500: "#9d8cbf",
// 600: "#9488b0",
// 700: "#8b83a1",
// 800: "#827d93",
// 900: "#797885",
// bg: "#f6f7ff",
// fg: "#75767d",
// a50: "#b0d7d8",
// a100: "#9ecedc",
// a200: "#94c1e0",
// a300: "#94b2e1",
// a400: "#9ca2db",
// a500: "#b180b7",
// a600: "#b27d98",
// a700: "#ae7c7c",
// a800: "#a47878",
// a900: "#927273"
// }
The perception()
action isn’t a destructive update for the same reasons as a11y()
.
An exporter that takes a complete color dict
and prepares it for a given palette format
.
format: gpl | sketchpalette
: the target palette formatdict: {}
: the input color dictionary
string
: file-ready exported palette
import {
convert,
palette,
output
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
const color = convert("hex", "rebeccapurple");
const contrast = 80;
const graphicPalette = palette({
configuration: "artistic",
contrast,
tints: 5,
tones: 3,
shades: 4,
accents: true,
}, color);
const graphicPaletteDark = palette({
configuration: "artistic",
contrast,
tints: 5,
tones: 3,
shades: 4,
accents: true,
dark: true
}, color);
const dict = {
project: {
name: "High Contrast Accessible Palette",
author: "Chatman R. Jr",
license: "Attribution 4.0 International (CC BY 4.0)",
version: "0.1.0"
},
day: graphicPalette,
night: graphicPaletteDark
};
// GIMP/Inkscape Palette
console.log(output("gpl", dict));
// GIMP Palette
// Name: High Contrast Accessible Palette (v0.1.0)
// # Owned by Chatman R. Jr
// # License: Attribution 4.0 International (CC BY 4.0)
// # 6/15/2022 10:53:31 PM
//
// Columns: 6
// 222 213 236 DAY BG (#ded5ec)
// 32 25 41 DAY FG (#201929)
// 85 46 126 DAY DARK 100 (#552e7e)
// 69 41 100 DAY DARK 200 (#452964)
// 53 35 75 DAY DARK 300 (#35234b)
// 38 28 52 DAY DARK 400 (#261c34)
// 110 0 112 DAY ACCENT 100 (#6e0070)
// 109 0 61 DAY ACCENT 200 (#6d003d)
// 101 0 0 DAY ACCENT 300 (#650000)
// 83 0 0 DAY ACCENT 400 (#530000)
// 32 25 41 NIGHT BG (#201929)
// 222 213 236 NIGHT FG (#ded5ec)
// 187 168 214 NIGHT LIGHT 100 (#bba8d6)
// 209 197 228 NIGHT LIGHT 200 (#d1c5e4)
// Sketch Palette
console.log(output("sketchpalette", dict));
// {"colors":[{"red":0.8705882352941177,"green":0.8352941176470589,"blue":0.9254901960784314,"alpha":1},{"red":0.12549019607843137,"green":0.09803921568627451,"blue":0.1607843137254902,"alpha":1},{"red":0.3333333333333333,"green":0.1803921568627451,"blue":0.49411764705882355,"alpha":1},{"red":0.27058823529411763,"green":0.1607843137254902,"blue":0.39215686274509803,"alpha":1},{"red":0.20784313725490197,"green":0.13725490196078433,"blue":0.29411764705882354,"alpha":1},{"red":0.14901960784313725,"green":0.10980392156862745,"blue":0.20392156862745098,"alpha":1},{"red":0.43137254901960786,"green":0,"blue":0.4392156862745098,"alpha":1},{"red":0.42745098039215684,"green":0,"blue":0.23921568627450981,"alpha":1},{"red":0.396078431372549,"green":0,"blue":0,"alpha":1},{"red":0.3254901960784314,"green":0,"blue":0,"alpha":1},{"red":0.12549019607843137,"green":0.09803921568627451,"blue":0.1607843137254902,"alpha":1},{"red":0.8705882352941177,"green":0.8352941176470589,"blue":0.9254901960784314,"alpha":1},{"red":0.7333333333333333,"green":0.6588235294117647,"blue":0.8392156862745098,"alpha":1},{"red":0.8196078431372549,"green":0.7725490196078432,"blue":0.8941176470588236,"alpha":1}],"pluginVersion":"1.4","compatibleVersion":"1.4"}
The content.js
module contains all functionality around creating content tokens.
The scale()
function handles all modular scale generation. It’s greatly improved from v1’s method. Instead of arbitrary
values, you pass in root CSS values. Use 1rem
when you mean it or 16px
whenever you want. The rest is done for
you.
The content module also contains utilities for handling other kinds of content concerns.
An action that takes a font
string and generates text tokens according to user settings
.
settings: {}
: text settingssettings.system = sans: sans | serif | monospace
: set system font stacksettings.weights = [regular, bold]: Array<thin | extralight | light | regular | medium | semibold | bold | extrabold | black>
: set text weights
font: string
: a custom font (or empty string)
{}
: generated text tokens
import { text } from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/content.js";
// Change the system stack
console.log(text({ system: "serif", weights: ["regular", "bold"] }, ""));
// {
// family: "Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro,...",
// regular: 400,
// bold: 700
// }
// Each weight string corresponds with a given generated weight token
console.log(text({ system: "sans", weights: ["thin", "light", "black"] }, ""));
// {
// family: "-apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, Ubuntu, roboto, n...",
// thin: 100,
// light: 300,
// black: 900
// }
// Set a font string and it will be prepended to the family
console.log(text({ system: "serif", weights: ["regular", "bold"] }, "Mozilla Slab"));
// {
// family: "Mozilla Slab, Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Sou...",
// regular: 400,
// bold: 700
// }
An action that takes a number of columns
and outputs grid tokens according to user settings
.
settings: {}
: grid settingsratio = 1.5: number
: grid fractions ratiorows: number
: number of grid rows to generate
columns: number
: number of grid columns to generate
{}
: the generated grid tokens
import { grid } from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/content.js";
// Rows will be set the same as columns if undefined
console.log(grid({}, 5));
// {
// columns: 5,
// rows: 5,
// col: {
// "1": 1,
// "2": 2,
// "3": 3,
// "4": 4,
// "5": 5,
// "-1": -1,
// "-2": -2,
// "-3": -3,
// "-4": -4,
// "-5": -5,
// fr: {
// base: "1fr",
// x2: "1.5fr",
// x3: "2.25fr",
// x4: "3.375fr",
// x5: "5.0625fr",
// d2: "0.66667fr",
// d3: "0.44444fr",
// d4: "0.2963fr",
// d5: "0.19753fr"
// }
// },
// row: {
// "1": 1,
// "2": 2,
// "3": 3,
// "4": 4,
// "5": 5,
// "-1": -1,
// "-2": -2,
// "-3": -3,
// "-4": -4,
// "-5": -5,
// fr: {
// base: "1fr",
// x2: "1.5fr",
// x3: "2.25fr",
// x4: "3.375fr",
// x5: "5.0625fr",
// d2: "0.66667fr",
// d3: "0.44444fr",
// d4: "0.2963fr",
// d5: "0.19753fr"
// }
// }
// }
// Set rows explicitly
console.log(grid({ rows: 2 }, 3));
// {
// columns: 3,
// rows: 2,
// col: {
// "1": 1,
// "2": 2,
// "3": 3,
// "-1": -1,
// "-2": -2,
// "-3": -3,
// fr: { base: "1fr", x2: "1.5fr", x3: "2.25fr", d2: "0.66667fr", d3: "0.44444fr" }
// },
// row: {
// "1": 1,
// "2": 2,
// "-1": -1,
// "-2": -2,
// fr: { base: "1fr", x2: "1.5fr", d2: "0.66667fr" }
// }
// }
// Set grid ratio
console.log(grid({ ratio: 1.2 }, 2))
// {
// columns: 2,
// rows: 2,
// col: {
// "1": 1,
// "2": 2,
// "-1": -1,
// "-2": -2,
// fr: { base: "1fr", x2: "1.2fr", d2: "0.83333fr" }
// },
// row: {
// "1": 1,
// "2": 2,
// "-1": -1,
// "-2": -2,
// fr: { base: "1fr", x2: "1.2fr", d2: "0.83333fr" }
// }
// }
// Set grid ratio (multithreaded)
console.log(grid({ rows: 4, ratio: [1.2, 1.4, 1.8] }, 10));
// {
// columns: 10,
// rows: 4,
// col: {
// "1": 1,
// "2": 2,
// "3": 3,
// "4": 4,
// "5": 5,
// "6": 6,
// "7": 7,
// "8": 8,
// "9": 9,
// "10": 10,
// "-1": -1,
// "-2": -2,
// "-3": -3,
// "-4": -4,
// "-5": -5,
// "-6": -6,
// "-7": -7,
// "-8": -8,
// "-9": -9,
// "-10": -10,
// fr: {
// base: "1fr",
// x2: "1.2fr",
// x3: "1.4fr",
// x4: "1.8fr",
// x5: "1.44fr",
// x6: "1.96fr",
// x7: "3.24fr",
// x8: "1.728fr",
// x9: "2.744fr",
// x10: "5.832fr",
// d2: "0.83333fr",
// d3: "0.71429fr",
// d4: "0.55556fr",
// d5: "0.69444fr",
// d6: "0.5102fr",
// d7: "0.30864fr",
// d8: "0.5787fr",
// d9: "0.36443fr",
// d10: "0.17147fr"
// }
// },
// row: {
// "1": 1,
// "2": 2,
// "3": 3,
// "4": 4,
// "-1": -1,
// "-2": -2,
// "-3": -3,
// "-4": -4,
// fr: {
// base: "1fr",
// x2: "1.2fr",
// x3: "1.4fr",
// x4: "1.8fr",
// d2: "0.83333fr",
// d3: "0.71429fr",
// d4: "0.55556fr"
// }
// }
// }
This function has your back for grid/subgrid value generation. Get as granular as you like.
An action that takes a root
CSS value and outputs a modular scale according to user settings
.
settings: {}
: scale token settingssettings.configuration = bidirectional: bidirectional | unidirectional | ranged
: set the scale configurationsettings.ratio = 1.5: number
: set the scale ratiosettings.values = 6: number
: the number of scale values to generatesettings.floor = 1: string | number
: set the range floor (ranged
)settings.trunc = false: boolean
: truncate the values? (ranged
)settings.reverse = false: boolean
: reverse the context? (ranged
)
root: string | number
: the generated scale tokens
{}
: the generated scale tokens
import { scale } from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/content.js";
// Roots can be unitless
console.log(scale({ configuration: "bidirectional" }, 1));
// {
// base: 1,
// x2: 1.5,
// x3: 2.25,
// x4: 3.375,
// x5: 5.0625,
// x6: 7.5938,
// d2: 0.66667,
// d3: 0.44444,
// d4: 0.2963,
// d5: 0.19753,
// d6: 0.13169
// }
console.log(scale({ configuration: "unidirectional" }, 1));
// {
// base: 1,
// x2: 1.5,
// x3: 2.25,
// x4: 3.375,
// x5: 5.0625,
// x6: 7.5938,
// }
// As rems
console.log(scale({ configuration: "bidirectional" }, "1rem"));
// {
// base: "1rem",
// x2: "1.5rem",
// x3: "2.25rem",
// x4: "3.375rem",
// x5: "5.0625rem",
// x6: "7.5938rem",
// d2: "0.66667rem",
// d3: "0.44444rem",
// d4: "0.2963rem",
// d5: "0.19753rem",
// d6: "0.13169rem"
// }
console.log(scale({ configuration: "unidirectional" }, "1rem"));
// {
// base: "1rem",
// x2: "1.5rem",
// x3: "2.25rem",
// x4: "3.375rem",
// x5: "5.0625rem",
// x6: "7.5938rem",
// }
// Set ratio
console.log(scale({ configuration: "bidirectional", ratio: 2 }, "1ex"))
// {
// base: "1ex",
// x2: "2ex",
// x3: "4ex",
// x4: "8ex",
// x5: "16ex",
// x6: "32ex",
// d2: "0.5ex",
// d3: "0.25ex",
// d4: "0.125ex",
// d5: "0.0625ex",
// d6: "0.03125ex"
// }
// Set ratio (multithread)
console.log(scale({ configuration: "bidirectional", ratio: [1.618, 2] }, "1ex"));
// {
// base: "1ex",
// x2: "1.618ex",
// x3: "2ex",
// x4: "2.6179ex",
// x5: "4ex",
// x6: "4.2358ex",
// d2: "0.61805ex",
// d3: "0.5ex",
// d4: "0.38199ex",
// d5: "0.25ex",
// d6: "0.23608ex"
// }
// Set values
console.log(scale({ configuration: "bidirectional", values: 4 }, "16px"));
// {
// base: "16px",
// x2: "24px",
// x3: "36px",
// x4: "54px",
// d2: "10.667px",
// d3: "7.1111px",
// d4: "4.7407px"
// }
import { scale } from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/content.js";
// IMPORTANT: pass in the maximum value as the root and set a floor
console.log(scale({ configuration: "ranged", floor: 2 }, "8px"));
// {
// base: "8px",
// i2: "6px",
// i3: "4.6667px",
// i4: "3.7778px",
// i5: "3.1852px",
// i6: "2.7901px",
// min: "2px"
// }
// truncate the result
console.log(scale({ configuration: "ranged", floor: 45, trunc: true }, "75ch"));
// { base: "75ch", i2: "65ch", i3: "58ch", i4: "53ch", i5: "50ch", i6: "48ch", min: "45ch" }
// reverse the context
console.log(scale({ configuration: "ranged", floor: 10, reverse: true }, "100%"));
// {
// base: "10%",
// i2: "21.852%",
// i3: "27.778%",
// i4: "36.667%",
// i5: "50%",
// i6: "70%",
// max: "100%"
// }
// Set the ratio
console.log(scale({ configuration: "ranged", floor: 4, ratio: 2 }, "8px"));
// {
// base: "8px",
// i2: "6px",
// i3: "5px",
// i4: "4.5px",
// i5: "4.25px",
// i6: "4.125px",
// min: "4px"
// }
// Set the ratio (multithread)
console.log(scale({ configuration: "ranged", floor: 4, ratio: [1.618, 2] }, "8px"));
// {
// base: "8px",
// i2: "6.4722px",
// i3: "6px",
// i4: "5.5279px",
// i5: "5px",
// i6: "4.9443px",
// min: "4px"
// }
// Set values
console.log(scale({ configuration: "ranged", floor: 250, values: 10 }, "1000ms"));
// {
// base: "1000ms",
// i2: "750ms",
// i3: "583.33ms",
// i4: "472.22ms",
// i5: "398.15ms",
// i6: "348.76ms",
// i7: "315.84ms",
// i8: "293.9ms",
// i9: "279.26ms",
// i10: "269.51ms",
// min: "250ms"
// }
The context of the root value changes depending on the configuration. In unidirectional/bidirectional
configurations,
the root is simply the initial value, but in the ranged
configuration, the root is the range target.
In addition, the floor can be unitless. This is powerful because it means the range values will take on the unit of the root value instead. If it has one.
Finally, be careful about how far apart your root and floor are. Also be aware of how many values you generate. Too many and the values won’t be distinct enough to use. If the root and floor are too close, same thing.
And if they’re far apart, you could end up with outrageous values.
Experiment and tweak the results until you get it just the way you want.
The exporter.js
module handles all the functionality around outputting token dictionaries for various
formats. QuarkSuite does not assume filesystem access, so the output is file-ready rather than a file itself. This
means you can directly write out to your filesystem using your environment’s native API or a library of your choice.
Exporters are defined by the domain target and include:
stylesheet()
: target is a stylesheet format (CSS custom properties, common preprocessors)data()
: target is a general data format (JSON, YAML)schema()
: target is another token schema (TailwindCSS theme, Style Dictionary tokens)
An exporter that takes a complete token dict
and prepares a file-ready template string for a given stylesheet format
.
format: css | scss | less | styl
: the target stylesheet formatdict: {}
: the input token dictionary
string
: file-ready stylesheet output
import {
convert,
palette,
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
import {
text,
scale,
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/content.js";
import { stylesheet } from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/exporter.js";
const color = convert("rgb", "rebeccapurple");
const ratio = 1.414;
const values: 4;
// Sample dictionary
const dict = {
project: {
name: "Sample Dictionary",
author: "Ed N. Bacon",
license: "MIT",
version: "0.1.0"
},
sd: {
color: palette({ configuration: "material", accents: true }, color),
text: {
primary: text({ system: "sans", weights: ["regular", "bold"] }, ""),
secondary: text({ system: "serif", weights: ["light", "bold", "black"] }, ""),
size: scale({ configuration: "bidirectional", ratio, values }, "1rem"),
measure: scale({ configuration: "ranged", floor: 45, trunc: true, ratio, values }, "75ch"),
leading: scale({ configuration: "ranged", floor: 1.25, ratio, values }, 1.5),
},
spacing: scale({ configuration: "bidirectional", ratio, values }, "1ex")
}
};
// CSS Custom Properties
console.log(stylesheet("css", dict));
// /**
// * Project: Sample Dictionary (v0.1.0)
// * Owned by: Ed N. Bacon
// * License: MIT
// * ================================================================================
// *
// * DESCRIPTION: N/A
// * COMMENTS: N/A
// * --------------------------------------------------------------------------------
// * Updated on 6/16/2022 at 5:53:22 PM
// **/
//
// :root {
// --sd-color-50: rgb(238, 234, 246);
// --sd-color-100: rgb(209, 197, 228);
// --sd-color-200: rgb(181, 161, 210);
// --sd-color-300: rgb(154, 125, 192);
// --sd-color-400: rgb(127, 89, 173);
// --sd-color-500: rgb(85, 46, 126);
// --sd-color-600: rgb(69, 41, 100);
// --sd-color-700: rgb(53, 35, 75);
// --sd-color-800: rgb(38, 28, 52);
// --sd-color-900: rgb(24, 21, 29);
// --sd-color-bg: rgb(255, 255, 255);
// --sd-color-fg: rgb(17, 17, 17);
// --sd-color-a50: rgb(109, 191, 179);
// --sd-color-a100: rgb(63, 172, 186);
// --sd-color-a200: rgb(41, 147, 192);
// --sd-color-a300: rgb(60, 118, 192);
// --sd-color-a400: rgb(85, 86, 180);
// --sd-color-a500: rgb(110, 0, 112);
// --sd-color-a600: rgb(109, 0, 61);
// --sd-color-a700: rgb(101, 0, 0);
// --sd-color-a800: rgb(83, 0, 0);
// --sd-color-a900: rgb(55, 0, 0);
// --sd-text-primary-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, Ubuntu, roboto, noto, segoe ui, arial, sans-serif;
// --sd-text-primary-regular: 400;
// --sd-text-primary-bold: 700;
// --sd-text-secondary-family: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
// --sd-text-secondary-light: 300;
// --sd-text-secondary-bold: 700;
// --sd-text-secondary-black: 900;
// --sd-text-size: 1rem;
// --sd-text-size-x2: 1.414rem;
// --sd-text-size-x3: 1.9994rem;
// --sd-text-size-x4: 2.8271rem;
// --sd-text-size-d2: 0.70721rem;
// --sd-text-size-d3: 0.50015rem;
// --sd-text-size-d4: 0.35372rem;
// --sd-text-measure: 75ch;
// --sd-text-measure-i2: 66ch;
// --sd-text-measure-i3: 60ch;
// --sd-text-measure-i4: 55ch;
// --sd-text-measure-min: 45ch;
// --sd-text-leading: 1.5;
// --sd-text-leading-i2: 1.4268;
// --sd-text-leading-i3: 1.375;
// --sd-text-leading-i4: 1.3384;
// --sd-text-leading-min: 1.25;
// --sd-spacing: 1ex;
// --sd-spacing-x2: 1.414ex;
// --sd-spacing-x3: 1.9994ex;
// --sd-spacing-x4: 2.8271ex;
// --sd-spacing-d2: 0.70721ex;
// --sd-spacing-d3: 0.50015ex;
// --sd-spacing-d4: 0.35372ex;
//
// }
// Sass (SCSS) variables
console.log(stylesheet("scss", dict));
//
// /*!
// * Project: Sample Dictionary (v0.1.0)
// * Owned by: Ed N. Bacon
// * License: MIT
// * ================================================================================
// *
// * DESCRIPTION: N/A
// * COMMENTS: N/A
// * --------------------------------------------------------------------------------
// * Updated on 6/16/2022 at 5:56:09 PM
// */
//
// $sd-color-50: rgb(238, 234, 246);
// $sd-color-100: rgb(209, 197, 228);
// $sd-color-200: rgb(181, 161, 210);
// $sd-color-300: rgb(154, 125, 192);
// $sd-color-400: rgb(127, 89, 173);
// $sd-color-500: rgb(85, 46, 126);
// $sd-color-600: rgb(69, 41, 100);
// $sd-color-700: rgb(53, 35, 75);
// $sd-color-800: rgb(38, 28, 52);
// $sd-color-900: rgb(24, 21, 29);
// $sd-color-bg: rgb(255, 255, 255);
// $sd-color-fg: rgb(17, 17, 17);
// $sd-color-a50: rgb(109, 191, 179);
// $sd-color-a100: rgb(63, 172, 186);
// $sd-color-a200: rgb(41, 147, 192);
// $sd-color-a300: rgb(60, 118, 192);
// $sd-color-a400: rgb(85, 86, 180);
// $sd-color-a500: rgb(110, 0, 112);
// $sd-color-a600: rgb(109, 0, 61);
// $sd-color-a700: rgb(101, 0, 0);
// $sd-color-a800: rgb(83, 0, 0);
// $sd-color-a900: rgb(55, 0, 0);
// $sd-text-primary-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, Ubuntu, roboto, noto, segoe ui, arial, sans-serif;
// $sd-text-primary-regular: 400;
// $sd-text-primary-bold: 700;
// $sd-text-secondary-family: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
// $sd-text-secondary-light: 300;
// $sd-text-secondary-bold: 700;
// $sd-text-secondary-black: 900;
// $sd-text-size: 1rem;
// $sd-text-size-x2: 1.414rem;
// $sd-text-size-x3: 1.9994rem;
// $sd-text-size-x4: 2.8271rem;
// $sd-text-size-d2: 0.70721rem;
// $sd-text-size-d3: 0.50015rem;
// $sd-text-size-d4: 0.35372rem;
// $sd-text-measure: 75ch;
// $sd-text-measure-i2: 66ch;
// $sd-text-measure-i3: 60ch;
// $sd-text-measure-i4: 55ch;
// $sd-text-measure-min: 45ch;
// $sd-text-leading: 1.5;
// $sd-text-leading-i2: 1.4268;
// $sd-text-leading-i3: 1.375;
// $sd-text-leading-i4: 1.3384;
// $sd-text-leading-min: 1.25;
// $sd-spacing: 1ex;
// $sd-spacing-x2: 1.414ex;
// $sd-spacing-x3: 1.9994ex;
// $sd-spacing-x4: 2.8271ex;
// $sd-spacing-d2: 0.70721ex;
// $sd-spacing-d3: 0.50015ex;
// $sd-spacing-d4: 0.35372ex;
//
// Less variables
console.log(stylesheet("less", dict));
//
// /*
// * Project: Sample Dictionary (v0.1.0)
// * Owned by: Ed N. Bacon
// * License: MIT
// * ================================================================================
// *
// * DESCRIPTION: N/A
// * COMMENTS: N/A
// * --------------------------------------------------------------------------------
// * Updated on 6/16/2022 at 5:57:30 PM
// */
//
// @sd-color-50: rgb(238, 234, 246);
// @sd-color-100: rgb(209, 197, 228);
// @sd-color-200: rgb(181, 161, 210);
// @sd-color-300: rgb(154, 125, 192);
// @sd-color-400: rgb(127, 89, 173);
// @sd-color-500: rgb(85, 46, 126);
// @sd-color-600: rgb(69, 41, 100);
// @sd-color-700: rgb(53, 35, 75);
// @sd-color-800: rgb(38, 28, 52);
// @sd-color-900: rgb(24, 21, 29);
// @sd-color-bg: rgb(255, 255, 255);
// @sd-color-fg: rgb(17, 17, 17);
// @sd-color-a50: rgb(109, 191, 179);
// @sd-color-a100: rgb(63, 172, 186);
// @sd-color-a200: rgb(41, 147, 192);
// @sd-color-a300: rgb(60, 118, 192);
// @sd-color-a400: rgb(85, 86, 180);
// @sd-color-a500: rgb(110, 0, 112);
// @sd-color-a600: rgb(109, 0, 61);
// @sd-color-a700: rgb(101, 0, 0);
// @sd-color-a800: rgb(83, 0, 0);
// @sd-color-a900: rgb(55, 0, 0);
// @sd-text-primary-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, Ubuntu, roboto, noto, segoe ui, arial, sans-serif;
// @sd-text-primary-regular: 400;
// @sd-text-primary-bold: 700;
// @sd-text-secondary-family: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
// @sd-text-secondary-light: 300;
// @sd-text-secondary-bold: 700;
// @sd-text-secondary-black: 900;
// @sd-text-size: 1rem;
// @sd-text-size-x2: 1.414rem;
// @sd-text-size-x3: 1.9994rem;
// @sd-text-size-x4: 2.8271rem;
// @sd-text-size-d2: 0.70721rem;
// @sd-text-size-d3: 0.50015rem;
// @sd-text-size-d4: 0.35372rem;
// @sd-text-measure: 75ch;
// @sd-text-measure-i2: 66ch;
// @sd-text-measure-i3: 60ch;
// @sd-text-measure-i4: 55ch;
// @sd-text-measure-min: 45ch;
// @sd-text-leading: 1.5;
// @sd-text-leading-i2: 1.4268;
// @sd-text-leading-i3: 1.375;
// @sd-text-leading-i4: 1.3384;
// @sd-text-leading-min: 1.25;
// @sd-spacing: 1ex;
// @sd-spacing-x2: 1.414ex;
// @sd-spacing-x3: 1.9994ex;
// @sd-spacing-x4: 2.8271ex;
// @sd-spacing-d2: 0.70721ex;
// @sd-spacing-d3: 0.50015ex;
// @sd-spacing-d4: 0.35372ex;
//
// Stylus variables
console.log(stylesheet("styl", dict));
//
// /*!
// * Project: Sample Dictionary (v0.1.0)
// * Owned by: Ed N. Bacon
// * License: MIT
// * ================================================================================
// *
// * DESCRIPTION: N/A
// * COMMENTS: N/A
// * --------------------------------------------------------------------------------
// * Updated on 6/16/2022 at 5:59:16 PM
// */
//
// sd-color-50 = rgb(238, 234, 246)
// sd-color-100 = rgb(209, 197, 228)
// sd-color-200 = rgb(181, 161, 210)
// sd-color-300 = rgb(154, 125, 192)
// sd-color-400 = rgb(127, 89, 173)
// sd-color-500 = rgb(85, 46, 126)
// sd-color-600 = rgb(69, 41, 100)
// sd-color-700 = rgb(53, 35, 75)
// sd-color-800 = rgb(38, 28, 52)
// sd-color-900 = rgb(24, 21, 29)
// sd-color-bg = rgb(255, 255, 255)
// sd-color-fg = rgb(17, 17, 17)
// sd-color-a50 = rgb(109, 191, 179)
// sd-color-a100 = rgb(63, 172, 186)
// sd-color-a200 = rgb(41, 147, 192)
// sd-color-a300 = rgb(60, 118, 192)
// sd-color-a400 = rgb(85, 86, 180)
// sd-color-a500 = rgb(110, 0, 112)
// sd-color-a600 = rgb(109, 0, 61)
// sd-color-a700 = rgb(101, 0, 0)
// sd-color-a800 = rgb(83, 0, 0)
// sd-color-a900 = rgb(55, 0, 0)
// sd-text-primary-family = -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, Ubuntu, roboto, noto, segoe ui, arial, sans-serif
// sd-text-primary-regular = 400
// sd-text-primary-bold = 700
// sd-text-secondary-family = Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol
// sd-text-secondary-light = 300
// sd-text-secondary-bold = 700
// sd-text-secondary-black = 900
// sd-text-size = 1rem
// sd-text-size-x2 = 1.414rem
// sd-text-size-x3 = 1.9994rem
// sd-text-size-x4 = 2.8271rem
// sd-text-size-d2 = 0.70721rem
// sd-text-size-d3 = 0.50015rem
// sd-text-size-d4 = 0.35372rem
// sd-text-measure = 75ch
// sd-text-measure-i2 = 66ch
// sd-text-measure-i3 = 60ch
// sd-text-measure-i4 = 55ch
// sd-text-measure-min = 45ch
// sd-text-leading = 1.5
// sd-text-leading-i2 = 1.4268
// sd-text-leading-i3 = 1.375
// sd-text-leading-i4 = 1.3384
// sd-text-leading-min = 1.25
// sd-spacing = 1ex
// sd-spacing-x2 = 1.414ex
// sd-spacing-x3 = 1.9994ex
// sd-spacing-x4 = 2.8271ex
// sd-spacing-d2 = 0.70721ex
// sd-spacing-d3 = 0.50015ex
// sd-spacing-d4 = 0.35372ex
//
An exporter that takes a complete token dict
and prepares a file-ready template string for a given data format
.
format: json | yaml
: the target data formatdict: {}
: the input token dictionary
string
: file-ready data output
import {
convert,
palette,
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
import {
text,
scale,
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/content.js";
import { data } from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/exporter.js";
const color = convert("rgb", "rebeccapurple");
const ratio = 1.414;
const values: 4;
// Sample dictionary
const dict = {
project: {
name: "Sample Dictionary",
author: "Ed N. Bacon",
license: "MIT",
version: "0.1.0"
},
sd: {
color: palette({ configuration: "material", accents: true }, color),
text: {
primary: text({ system: "sans", weights: ["regular", "bold"] }, ""),
secondary: text({ system: "serif", weights: ["light", "bold", "black"] }, ""),
size: scale({ configuration: "bidirectional", ratio, values }, "1rem"),
measure: scale({ configuration: "ranged", floor: 45, trunc: true, ratio, values }, "75ch"),
leading: scale({ configuration: "ranged", floor: 1.25, ratio, values }, 1.5),
},
spacing: scale({ configuration: "bidirectional", ratio, values }, "1ex")
}
};
// JSON
console.log(data("json", dict));
// {
// "project": {
// "name": "Sample Dictionary",
// "author": "Ed N. Bacon",
// "license": "MIT",
// "version": "0.1.0"
// },
// "tokens": {
// "sd": {
// "color": {
// "50": "rgb(238, 234, 246)",
// "100": "rgb(209, 197, 228)",
// "200": "rgb(181, 161, 210)",
// "300": "rgb(154, 125, 192)",
// "400": "rgb(127, 89, 173)",
// "500": "rgb(85, 46, 126)",
// "600": "rgb(69, 41, 100)",
// "700": "rgb(53, 35, 75)",
// "800": "rgb(38, 28, 52)",
// "900": "rgb(24, 21, 29)",
// "bg": "rgb(255, 255, 255)",
// "fg": "rgb(17, 17, 17)",
// "a50": "rgb(109, 191, 179)",
// "a100": "rgb(63, 172, 186)",
// "a200": "rgb(41, 147, 192)",
// "a300": "rgb(60, 118, 192)",
// "a400": "rgb(85, 86, 180)",
// "a500": "rgb(110, 0, 112)",
// "a600": "rgb(109, 0, 61)",
// "a700": "rgb(101, 0, 0)",
// "a800": "rgb(83, 0, 0)",
// "a900": "rgb(55, 0, 0)"
// },
// "text": {
// "primary": {
// "family": "-apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, Ubuntu, roboto, noto, segoe ui, arial, sans-serif",
// "regular": 400,
// "bold": 700
// },
// "secondary": {
// "family": "Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol",
// "light": 300,
// "bold": 700,
// "black": 900
// },
// "size": {
// "base": "1rem",
// "x2": "1.414rem",
// "x3": "1.9994rem",
// "x4": "2.8271rem",
// "d2": "0.70721rem",
// "d3": "0.50015rem",
// "d4": "0.35372rem"
// },
// "measure": {
// "base": "75ch",
// "i2": "66ch",
// "i3": "60ch",
// "i4": "55ch",
// "min": "45ch"
// },
// "leading": {
// "base": 1.5,
// "i2": 1.4268,
// "i3": 1.375,
// "i4": 1.3384,
// "min": 1.25
// }
// },
// "spacing": {
// "base": "1ex",
// "x2": "1.414ex",
// "x3": "1.9994ex",
// "x4": "2.8271ex",
// "d2": "0.70721ex",
// "d3": "0.50015ex",
// "d4": "0.35372ex"
// }
// }
// }
// }
// YAML
console.log(data("yaml", dict));
//
// # Updated on 6/16/2022 at 6:05:31 PM
//
// project:
// name: Sample Dictionary
// author: Ed N. Bacon
// license: MIT
// version: 0.1.0
//
// tokens:
// sd:
// color:
// 50: rgb(238, 234, 246)
// 100: rgb(209, 197, 228)
// 200: rgb(181, 161, 210)
// 300: rgb(154, 125, 192)
// 400: rgb(127, 89, 173)
// 500: rgb(85, 46, 126)
// 600: rgb(69, 41, 100)
// 700: rgb(53, 35, 75)
// 800: rgb(38, 28, 52)
// 900: rgb(24, 21, 29)
// bg: rgb(255, 255, 255)
// fg: rgb(17, 17, 17)
// a50: rgb(109, 191, 179)
// a100: rgb(63, 172, 186)
// a200: rgb(41, 147, 192)
// a300: rgb(60, 118, 192)
// a400: rgb(85, 86, 180)
// a500: rgb(110, 0, 112)
// a600: rgb(109, 0, 61)
// a700: rgb(101, 0, 0)
// a800: rgb(83, 0, 0)
// a900: rgb(55, 0, 0)
// text:
// primary:
// family: -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, Ubuntu, roboto, noto, segoe ui, arial, sans-serif
// regular: 400
// bold: 700
// secondary:
// family: Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol
// light: 300
// bold: 700
// black: 900
// size:
// base: 1rem
// x2: 1.414rem
// x3: 1.9994rem
// x4: 2.8271rem
// d2: 0.70721rem
// d3: 0.50015rem
// d4: 0.35372rem
// measure:
// base: 75ch
// i2: 66ch
// i3: 60ch
// i4: 55ch
// min: 45ch
// leading:
// base: 1.5
// i2: 1.4268
// i3: 1.375
// i4: 1.3384
// min: 1.25
// spacing:
// base: 1ex
// x2: 1.414ex
// x3: 1.9994ex
// x4: 2.8271ex
// d2: 0.70721ex
// d3: 0.50015ex
// d4: 0.35372ex
//
An exporter that takes a complete token dict
and translates the data to a given format
schema.
format: tailwindcss | style-dictionary
: the target schemadict: {}
: the input token dictionary
{}
: the output data
import {
convert,
palette,
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
import {
text,
scale,
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/content.js";
import { schema } from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/exporter.js";
const color = convert("rgb", "rebeccapurple");
const ratio = 1.414;
const values: 4;
// Sample dictionary
const dict = {
project: {
name: "Sample Dictionary",
author: "Ed N. Bacon",
license: "MIT",
version: "0.1.0"
},
sd: {
color: palette({ configuration: "material", accents: true }, color),
text: {
primary: text({ system: "sans", weights: ["regular", "bold"] }, ""),
secondary: text({ system: "serif", weights: ["light", "bold", "black"] }, ""),
size: scale({ configuration: "bidirectional", ratio, values }, "1rem"),
measure: scale({ configuration: "ranged", floor: 45, trunc: true, ratio, values }, "75ch"),
leading: scale({ configuration: "ranged", floor: 1.25, ratio, values }, 1.5),
},
spacing: scale({ configuration: "bidirectional", ratio, values }, "1ex")
}
};
// TailwindCSS theme
console.log(schema("tailwindcss", {
project: {},
theme: {
extend: {
colors: { main: dict.sd.color },
spacing: dict.sd.spacing
},
}
}));
// {
// theme: {
// extend: {
// colors: {
// main: {
// 50: "rgb(238, 234, 246)",
// 100: "rgb(209, 197, 228)",
// 200: "rgb(181, 161, 210)",
// 300: "rgb(154, 125, 192)",
// 400: "rgb(127, 89, 173)",
// 500: "rgb(85, 46, 126)",
// 600: "rgb(69, 41, 100)",
// 700: "rgb(53, 35, 75)",
// 800: "rgb(38, 28, 52)",
// 900: "rgb(24, 21, 29)",
// bg: "rgb(255, 255, 255)",
// fg: "rgb(17, 17, 17)",
// a50: "rgb(109, 191, 179)",
// a100: "rgb(63, 172, 186)",
// a200: "rgb(41, 147, 192)",
// a300: "rgb(60, 118, 192)",
// a400: "rgb(85, 86, 180)",
// a500: "rgb(110, 0, 112)",
// a600: "rgb(109, 0, 61)",
// a700: "rgb(101, 0, 0)",
// a800: "rgb(83, 0, 0)",
// a900: "rgb(55, 0, 0)"
// }
// },
// spacing: {
// DEFAULT: "1ex",
// x2: "1.414ex",
// x3: "1.9994ex",
// x4: "2.8271ex",
// d2: "0.70721ex",
// d3: "0.50015ex",
// d4: "0.35372ex"
// }
// }
// }
// }
// Style Dictionary tokens
console.log(schema("style-dictionary", { project: {}, ...dict }));
// {
// sd: {
// color: {
// 50: {
// value: "rgb(238, 234, 246)"
// },
// 100: {
// value: "rgb(209, 197, 228)"
// },
// 200: {
// value: "rgb(181, 161, 210)"
// },
// 300: {
// value: "rgb(154, 125, 192)"
// },
// 400: {
// value: "rgb(127, 89, 173)"
// },
// 500: {
// value: "rgb(85, 46, 126)"
// },
// 600: {
// value: "rgb(69, 41, 100)"
// },
// 700: {
// value: "rgb(53, 35, 75)"
// },
// 800: {
// value: "rgb(38, 28, 52)"
// },
// 900: {
// value: "rgb(24, 21, 29)"
// },
// bg: {
// value: "rgb(255, 255, 255)"
// },
// fg: {
// value: "rgb(17, 17, 17)"
// },
// a50: {
// value: "rgb(109, 191, 179)"
// },
// a100: {
// value: "rgb(63, 172, 186)"
// },
// a200: {
// value: "rgb(41, 147, 192)"
// },
// a300: {
// value: "rgb(60, 118, 192)"
// },
// a400: {
// value: "rgb(85, 86, 180)"
// },
// a500: {
// value: "rgb(110, 0, 112)"
// },
// a600: {
// value: "rgb(109, 0, 61)"
// },
// a700: {
// value: "rgb(101, 0, 0)"
// },
// a800: {
// value: "rgb(83, 0, 0)"
// },
// a900: {
// value: "rgb(55, 0, 0)"
// }
// },
// text: {
// primary: {
// family: {
// value: "-apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, Ubuntu, roboto, noto, segoe ui, arial, sans-serif"
// },
// regular: {
// value: 400
// },
// bold: {
// value: 700
// }
// },
// secondary: {
// family: {
// value: "Iowan Old Style, Apple Garamond, Baskerville, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol"
// },
// light: {
// value: 300
// },
// bold: {
// value: 700
// },
// black: {
// value: 900
// }
// },
// size: {
// base: {
// value: "1rem"
// },
// x2: {
// value: "1.414rem"
// },
// x3: {
// value: "1.9994rem"
// },
// x4: {
// value: "2.8271rem"
// },
// d2: {
// value: "0.70721rem"
// },
// d3: {
// value: "0.50015rem"
// },
// d4: {
// value: "0.35372rem"
// }
// },
// measure: {
// base: {
// value: "75ch"
// },
// i2: {
// value: "66ch"
// },
// i3: {
// value: "60ch"
// },
// i4: {
// value: "55ch"
// },
// min: {
// value: "45ch"
// }
// },
// leading: {
// base: {
// value: 1.5
// },
// i2: {
// value: 1.4268
// },
// i3: {
// value: 1.375
// },
// i4: {
// value: 1.3384
// },
// min: {
// value: 1.25
// }
// }
// },
// spacing: {
// base: {
// value: "1ex"
// },
// x2: {
// value: "1.414ex"
// },
// x3: {
// value: "1.9994ex"
// },
// x4: {
// value: "2.8271ex"
// },
// d2: {
// value: "0.70721ex"
// },
// d3: {
// value: "0.50015ex"
// },
// d4: {
// value: "0.35372ex"
// }
// }
// }
// }
The workflow.js
contains functionality around QuarkSuite’s advanced data composition and reuse.
It’s important to note that the workflow module changes nothing about the way data is actually generated. Its main purpose is to expose design patterns helpful to the advanced user.
Collectively, the workflow module replaces the configuration and formula idioms that featured in v1 (if you’re familiar with the last version).
A workflow helper that converts an action
into an emitter by presetting its y
modifier.
action: (y: unknown, x: unknown) => unknown>
: the action to converty: unknown
: the action modifier to preset
(x: unknown) => unknown
: the resultant emitter
import {
convert,
adjust,
mix
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
import { preset } from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/workflow.js";
const swatch = "rebeccapurple";
const hex = preset(convert, "hex");
const adjacent = preset(adjust, { hue: 30 });
const withRed = preset(mix, { target: "crimson", strength: 30 });
// swatch | hex -> adjacent -> withRed
console.log(withRed(adjacent(hex(swatch)))); // #9d276a
// order matters
console.log(adjacent(withRed(hex(swatch)))); // #99315a
// any color | hex -> adjacent -> withRed
console.log(withRed(adjacent(hex("dodgerblue")))); // #a46dc7
console.log(withRed(adjacent(hex("#ea7")))); // #de925b
console.log(withRed(adjacent(hex("rgb(25, 128, 33)")))); // #747558
console.log(withRed(adjacent(hex("hsl(210, 40%, 60%)")))); // #aa7a9d
Using preset()
to convert actions into emitters is the starting point of all advanced QuarkSuite usage. The remaining
helpers deal exclusively with harnessing the power unleashed by this simple function.
A workflow helper that combines a sequence of emitters
into a new emitter.
emitters: Array<(x: unknown) => unknown>
: the sequence of emitters to combine
(x: unknown) => unknown
: a new emitter from execution sequence
import {
convert,
adjust,
mix
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
import {
preset,
process
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/workflow.js";
const swatch = "rebeccapurple";
const hex = preset(convert, "hex");
const adjacent = preset(adjust, { hue: 30 });
const withRed = preset(mix, { target: "crimson", strength: 30 });
// Create primary (color) process
const primary = process(hex, adjacent, withRed);
// Processes are composable from other processes
const secondary = process(primary, adjacent);
const tertiary = process(secondary, adjacent, adjacent);
console.log(primary(swatch)); // #9d276a
console.log(secondary(swatch)) // #a72433
console.log(tertiary(swatch)); // #934800
Creating a process
is ideal in situations where you want to repeat a sequence of transformations on different values
of the same type, but you don’t know ahead of time exactly what those values will be.
A workflow helper that transforms data of type x
through a sequence of emitters
.
x: unknown
: the data to pass throughemitters: Array<(x: unknown) => unknown>
: the sequence of emitters to execute
unknown
: the transformed data
import {
convert,
adjust,
mix
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
import {
preset,
process,
pipeline
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/workflow.js";
const swatch = "rebeccapurple";
const hex = preset(convert, "hex");
const adjacent = preset(adjust, { hue: 30 });
const withRed = preset(mix, { target: "crimson", strength: 30 });
// Create primary (color) process
const primary = process(hex, adjacent, withRed);
const main = pipeline(swatch, primary);
// The output of a pipeline can be used as the input of other pipelines
const accent = pipeline(main, adjacent);
const highlight = pipeline(accent, adjacent, adjacent, withRed);
console.log({ main, accent, highlight });
// { main: "#9d276a", accent: "#a72433", highlight: "#a9411a" }
A pipeline can be composed of a sequence of processes (because they are themselves emitters). A process can use the output of a pipeline as its data.
A workflow helper that maps the execution result of an emitter
to a collection of xs
.
emitter: (x: unknown) => unknown
: the emitter to execute on each valuexs: unknown[]
: the collection of values
unknown[]
: the collection of results
import {
convert,
mix,
harmony
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
import {
preset,
propagate
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/workflow.js";
const swatch = convert("hex", "rebeccapurple");
const scheme = preset(harmony, { configuration: "analogous" });
const light = preset(mix, {target: "#fff", strength: 75, steps: 3});
const dark = preset(mix, {target: "#111", strength: 75, steps: 3});
// Propagate over scheme to create variants
console.log(propagate(light, scheme(swatch)));
// [
// [ "#8967b4", "#af99ce", "#d6cbe7" ],
// [ "#a45d98", "#c492ba", "#e2c8dc" ],
// [ "#b35973", "#d0909f", "#e9c7ce" ]
// ]
console.log(propagate(dark, scheme(swatch)));
// [
// [ "#4f2c74", "#392451", "#241b30" ],
// [ "#64225b", "#471e41", "#2c1928" ],
// [ "#701c3a", "#4f1b2c", "#30181e" ]
// ]
Propagation is a tool for when you have a scale of values and want to transform each value with the same emitter. You can also use it to transform arbitrary groupings of values as long as they’re the same type.
Not demonstrated but important to know: propagate()
can be preset. This means you can slip propagation into processes
and pipelines. From here, even more options for composing complex data open up to the advanced user.
A workflow helper that maps a collection of xs
to a sequence of emitters
.
xs: unknown[]
: the collection to delegateemitters: Array<(x: unknown) => unknown>
: the sequence of emitters
unknown[]
: the collection of results
import {
convert,
adjust,
harmony
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/color.js";
import {
preset,
delegate
} from "https://cdn.jsdelivr.net/gh/quarksuite/core@2.1.0/workflow.js";
const swatch = convert("hex", "rebeccapurple");
const scheme = preset(harmony, { configuration: "analogous" });
// Adjust colors in scheme for greater contrast
console.log(
delegate(
scheme(swatch),
undefined,
preset(adjust, { lightness: 20, chroma: -10 }),
preset(adjust, { lightness: -15, chroma: 10 })
)
); // ["#663399", "#bd6baf", "#680021"]
Reach for delegate()
when you want to assign emitters to specific values. It’s really powerful for grouping and
distributing processes across different sets of data.
If you want to extend the library or build particular functionality on top of it, you can do that without much hassle. QuarkSuite is small and intentionally designed (especially in the shift from v1 to v2) to allow it.
The key is awareness of the types of data that QuarkSuite passes around and its simple architectural idioms.
As long as your extensions follow the minimal rules set by the design, you can build as many layers of abstraction on top as you or your team require.
If you run into any issues with this, please let me know how I can help.
Now that you have the technical overview of how this all works, give it a try in your own projects.
I highly recommend you follow up this reading with the user handbook if you haven’t already. Quite a bit of this documentation makes better sense in context. Especially advanced usage through the workflow module.
Thanks for reading, and I hope you found it useful.