Skip to content

Latest commit

 

History

History
2963 lines (2570 loc) · 80.6 KB

API.org

File metadata and controls

2963 lines (2570 loc) · 80.6 KB

QuarkSuite Core API (v2.1.0)

Table of Contents

Summary

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.

Concepts

QuarkSuite Core has certain idioms based around its design, and it helps to be aware of them while reading.

Types

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 string
  • palette: generated palette tokens
  • root: any CSS value (unitless allowed)
  • dict: a wrapped token dictionary with a required project property for exporting

Any type that is not explicitly mentioned here is local to the given utility.

Tokens

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.

Dictionary

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.

Idioms

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 type x and a y modifier to configure its output
  • exporter(format, dict): a special action that accepts type dict and transforms its output based on a target format

Emitters

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 of x

Converting actions into emitters is the driving force of advanced QuarkSuite workflows.

Exporter

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.

API

Color

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.

convert(to, color)

An action that takes any valid CSS color and converts it to a given target format.

Parameters

  • to: hex | rgb | hsl | cmyk | hwb | lab | lch | oklab | oklch: the target format
  • color: string: the input color

Returns

  • string: the converted color

Example

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)

Notes

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.

adjust(settings, color)

An action that takes any valid CSS color and adjusts its properties according to user settings.

Parameters

  • settings: {}: color adjustment settings
    • settings.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

Returns

  • string | string[]: the adjusted color or interpolation data

Example

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)"
// ]

Notes

  • Percentage values lock at ±0-100
  • Hue locks at ±0-360

mix(settings, color)

An action that takes any valid CSS color and mixes it according to user settings.

Parameters

  • settings: {}: color blending settings
    • settings.target = color: string: set the blend target
    • settings.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

Returns

  • string | string[]: the blended color or interpolation data

Example

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%)"
// ]

Notes

  • Percentage values lock at ±0-100

harmony(settings, color)

An action that takes any valid CSS color and a generates an artistic color harmony according to user settings.

Parameters

  • settings: {}: color harmony settings
    • settings.configuration = complementary: dyadic | complementary | analogous | split | triadic | clash | double | tetradic | square: set the color harmony configuration
    • settings.accented = false: boolean: accented variant? (with dyadic, analogous, split, triadic)
  • color: string: the input color

Returns

  • string[]: the generated color harmony

Example

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" ]

palette(settings, color)

An action that takes any valid CSS color and generates a palette according to user settings.

Parameters

  • settings: {}: palette settings
    • settings.configuration = material: material | artistic: set the palette configuration
    • settings.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

Returns

  • {}: the generated palette tokens

Examples

Material Configuration
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" }
// }
Artistic Configuration
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"
//   }
// }
Color Perception

Notes

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.

a11y(settings, palette)

An action that takes a generated palette and filters it for accessibility according to user settings.

Parameters

  • settings: color accessibility filter settings
    • settings.mode: standard | custom: set color accessibility mode
    • settings.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

Returns

{}: the filtered palette

Examples

WCAG (Standard)
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"
// }
Colorimetric Comparison (Custom)
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" }

Notes

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.

perception(settings, palette)

An action that takes a generated palette and overlays a color perception simulator according to user settings.

Parameters

  • settings: {}: color perception simulation settings
    • settings.check: vision | contrast | illuminant: set simulation target
    • settings.severity = 50: number: set severity of simulation (where applicable)
    • settings.as = protanopia: achromatopsia | protanomaly | protanopia | deuteranomaly | deuteranopia | tritanomaly | tritanopia: set colorblindness to target
    • settings.method = brettel: brettel | vienot: set colorblindness algorithm to use
    • settings.factor = 0: number: set contrast sensitivity gray factor (as a percentage)
    • settings.K = 1850: number: set illuminant temperature (in kelvins)
  • palette: {}: generated palette

Returns

{}: the simulated palette

Examples

Color Vision Simulator
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"
// }
Contrast Sensitivity Simulator
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"
// }
Illuminant Simulator
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"
// }

Notes

The perception() action isn’t a destructive update for the same reasons as a11y().

output(format, dict)

An exporter that takes a complete color dict and prepares it for a given palette format.

Parameters

  • format: gpl | sketchpalette: the target palette format
  • dict: {}: the input color dictionary

Returns

string: file-ready exported palette

Example

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"}

Content

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.

text(settings, font)

An action that takes a font string and generates text tokens according to user settings.

Parameters

  • settings: {}: text settings
    • settings.system = sans: sans | serif | monospace: set system font stack
    • settings.weights = [regular, bold]: Array<thin | extralight | light | regular | medium | semibold | bold | extrabold | black>: set text weights
  • font: string: a custom font (or empty string)

Returns

{}: generated text tokens

Example

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
// }

grid(settings, columns)

An action that takes a number of columns and outputs grid tokens according to user settings.

Parameters

  • settings: {}: grid settings
    • ratio = 1.5: number: grid fractions ratio
    • rows: number: number of grid rows to generate
  • columns: number: number of grid columns to generate

Returns

{}: the generated grid tokens

Examples

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"
//     }
//   }
// }

Notes

This function has your back for grid/subgrid value generation. Get as granular as you like.

scale(settings, root)

An action that takes a root CSS value and outputs a modular scale according to user settings.

Parameters

  • settings: {}: scale token settings
    • settings.configuration = bidirectional: bidirectional | unidirectional | ranged: set the scale configuration
    • settings.ratio = 1.5: number: set the scale ratio
    • settings.values = 6: number: the number of scale values to generate
    • settings.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

Returns

{}: the generated scale tokens

Examples

Bidirectional/Unidirectional
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"
// }
Ranged
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"
// }

Notes

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.

Exporter

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)

stylesheet(format, dict)

An exporter that takes a complete token dict and prepares a file-ready template string for a given stylesheet format.

Parameters

  • format: css | scss | less | styl: the target stylesheet format
  • dict: {}: the input token dictionary

Returns

string: file-ready stylesheet output

Example

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
//

data(format, dict)

An exporter that takes a complete token dict and prepares a file-ready template string for a given data format.

Parameters

  • format: json | yaml: the target data format
  • dict: {}: the input token dictionary

Returns

string: file-ready data output

Example

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
//

schema(format, dict)

An exporter that takes a complete token dict and translates the data to a given format schema.

Parameters

  • format: tailwindcss | style-dictionary: the target schema
  • dict: {}: the input token dictionary

Returns

{}: the output data

Example

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"
//       }
//     }
//   }
// }

Workflow

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).

preset(action, y)

A workflow helper that converts an action into an emitter by presetting its y modifier.

Parameters

  • action: (y: unknown, x: unknown) => unknown>: the action to convert
  • y: unknown: the action modifier to preset

Returns

  • (x: unknown) => unknown: the resultant emitter

Example

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

Notes

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.

process(…emitters)

A workflow helper that combines a sequence of emitters into a new emitter.

Parameters

  • emitters: Array<(x: unknown) => unknown>: the sequence of emitters to combine

Returns

  • (x: unknown) => unknown: a new emitter from execution sequence

Example

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

Notes

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.

pipeline(x, …emitters)

A workflow helper that transforms data of type x through a sequence of emitters.

Parameters

  • x: unknown: the data to pass through
  • emitters: Array<(x: unknown) => unknown>: the sequence of emitters to execute

Returns

unknown: the transformed data

Example

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" }

Notes

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.

propagate(emitter, xs)

A workflow helper that maps the execution result of an emitter to a collection of xs.

Parameters

  • emitter: (x: unknown) => unknown: the emitter to execute on each value
  • xs: unknown[]: the collection of values

Returns

unknown[]: the collection of results

Example

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" ]
// ]

Notes

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.

delegate(xs, …emitters)

A workflow helper that maps a collection of xs to a sequence of emitters.

Parameters

  • xs: unknown[]: the collection to delegate
  • emitters: Array<(x: unknown) => unknown>: the sequence of emitters

Returns

  • unknown[]: the collection of results

Example

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"]

Notes

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.

Extending the Library

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.

Next Steps

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.