๐Ÿ”ฎ Scheme

The myriad function is the main function that handles everything. It takes a scheme and a settings object, both of which are optional and whos paramaters all have defaults.

myriad(scheme, settings)

The scheme comprises multiple color parameters that can be categorized into groups. These include the main color parameters, such as the background, foreground, and accents, along with additional custom colors and sub-schemes. These groups make it easier to manage and modify color themes in a structured and organized manner

const scheme = {
  background: '#090233',
  foreground: '#ff5555',
  accents: ['#5200ff'],
  custom: {
    foo: '#00ff00',
    bar: '#ff0000',
  },
  subSchemes: {}
}

๐Ÿ”ฎ Schematic Colors

The main colors, specifically the foreground and background, are the primary factors that determine the color scheme. It is important that these two colors have sufficient contrast, and the Myriad system ensures this. The available range of colors is effectively dictated by the foreground and background.

Additional shades are generated as a mix within this range. By categorizing colors as either background or foreground, the system can better understand color readability and context. Accent colors are commonly used as brand colors.

const scheme = {
  background: '#090233',
  foreground: '#ff5555',
  accents: ['#5200ff'],
}

๐Ÿ”ฎ Custom Colors

Custom colors allow you to supplement the semantic colors with more specific named colors. While it is generally recommended to use semantic colors whenever possible, custom colors can be useful for colors that are exceptions to the rest of the theme, such as success colors, error colors, or link colors.

By default, custom colors are treated like accent colors. However, you can take control over this by passing functions as the values instead of strings. They are assigned a contrast color, and shades are generated using the same method as with accent colors. They are also adjusted for readability in the same way as accent colors.

const custom: {
  foo: '#00ff00',
  bar: '#ff0000',
},

Heres an example thats a little closer to the practical use case

const custom: {
  success: '#00ff00',
  error: '#ff0000',
  link: '#0000ff',
},

๐Ÿงฌ Primitives

Unlike the other colors, custom colors can be assigned with functions. This allows you to take control of the system to create your own color logic if you want something other than the standard way that the generator handles accent/custom colors.

In this example, we are providing a custom color called "link" with a function that simply returns a plain blue color without any adjustment. This is likely not a good idea since this color will not be adjusted for readability. However, it is the simplest example of how to use this feature and could be useful if you need to guarantee no changes to this specific color while leaving the rest of the system intact.

myriad({
  foreground: '#ffffff',
  custom: {
    link: () => {
      return '#0000ff'
    }
  }
})

Here's a slightly more advanced version where we effectively do the same thing that happens by default, except we are handling it ourselves by importing the primitives from /core:

import { myriad, Myriad, getReadable } from "@myriadjs/core"

function linkColor(generated: Myriad) {
  if(!generated.foreground) return "black"
  return getReadable('#6b6bff', generated, 7)
}

myriad({
  foreground: '#ffffff',
  custom: {
    link: linkColor
  }
})

๐Ÿ”ฎ SubSchemes

This is where the Myriad approach goes from being simple and clean to being extremely flexible and powerful. The entire system is based on three colors - foreground, background, and accents. But sometimes you may want a more complex theme with additional colors. For example, you may need a header with a different background color. While you can add custom colors and multiple accents, these should be used sparingly because they are not intended to control large parts of the theme.

The obvious solution is to run multiple Myriad generators with different schemes that are attached to different elements. However, this can quickly become unwieldy and difficult to manage.

Enter SubSchemes - a way to create custom schemes within a larger scheme. With SubSchemes, you can add additional colors and customize the look of specific parts of your design without having to create entirely new schemes.

To use SubSchemes, you define a new scheme object within the main scheme and provide it with its own set of color parameters. These colors will then be available to use within the SubScheme, and the Myriad system will generate shades and adjust contrast based on the main scheme's foreground, background, and accent colors.

By using SubSchemes, you can create more complex and customizable themes without sacrificing the simplicity and ease-of-use that makes Myriad so powerful.

import { myriad } from "@myriadjs/core"

//Theme for the entire site
myriad({
  background: '#0c0915',
  foreground: '#c0aea3',
  accents: ['#c97074'],
}).attach()

//Theme for the header
myriad({
  background: '#c0aea3',
  foreground: '#0c0915',
  accents: ['#c97074'],
}).attach(document.querySelector('.header'))

SubSchemes are a way to create a new scheme that inherits from the main scheme. This means that you can create a new scheme that only has to define the colors that are diffirent from the main scheme. It also means that you can describe the entire scheme from a single source, including sub schemes.

import { myriad } from "@myriadjs/core"

myriad({
  background: '#0c0915',
  foreground: '#c0aea3',
  accents: ['#c97074'],
  subSchemes: {
    header: {
      background: '#c0aea3',
      foreground: '#0c0915',
    }
  }
})

Heres a more generic example to hammer home the point that these subSchemes are all arbitrary.

myriad({
  background: '#0c0915',
  foreground: '#c0aea3',
  accents: ['#c97074'],
  subSchemes: {
    foo: {
      background: '#c0aea3',
    },
    bar: {
      accents: ['#c0aea3'],
      foreground: '#0c0915',
    },
  }
})

But this wont work right off the bat. Because how does the system know where to attach the subScheme colors? The inbuilt subScheme handler to the rescue.

import { myriad, subScheme } from "@myriadjs/core"

const theme = myriad({
  background: '#0c0915',
  foreground: '#c0aea3',
  accents: ['#c97074'],
  subSchemes: {
    header: {
      accents: ['#c0aea3'],
      foreground: '#0c0915',
    },
  }
}).attach()


subScheme(theme, {
  id: 'header',
  element: document.querySelector('.header') //*note
}).attach()

Note This just modifies the default when you call attach. You could commit this and instead reference the element in the attach call. This same setting also exists on then myriad function itself.

This is just a simple handler that takes a myriad instance and a subScheme object. The subScheme object has an id and an element. The id is the name of the subScheme and the element is the element that the subScheme should be attached to. This way you can generate the scheme once, pass the result to whatever page or component you want and then attach the subSchemes to the elements that need them. This lets you describe an infinitely complex theme using the same simple 3 color system.

More importantly, since its all CSS variables, you can increase then complexity of the theme without increasing the complexity of the code. As long as you keep using the same CSS variables everywhere in your CSS - you can at any point change what those colors mean for the entire site with teh myriad function, or for a specific element with the subScheme function.

Infinite scalability without rewriting anything

The beauty of this approach lies in its ability to balance simplicity and flexibility seamlessly. It offers both scalability and simplicity, enabling you to modify colors or add/remove theme sections effortlessly, without requiring any code rewriting.

The subScheme handler is very simple and you could even make it yourself. Heres the entire internals of this function.

interface SubSchemeProps {
  id: string
  element: HTMLElement
  settings?: MyriadSettings
}

export const subScheme = (scheme: Myriad, props: SubSchemeProps) => {
  let subSchemes = scheme.subSchemes
  if(subSchemes === undefined) return null
  return myriad(subSchemes[props.id], props.settings)
}