Skip to content

fw.css

Funkwhale has a single stylesheet for all projects (Website, Docs, App) and for anyone who wants to use the Funkwhale design system.

Design system layers

  1. Cross-browser sanitization makes sure every browser displays the design consistently.
  2. Document styles give standard HTML elements such as <h1> a consistent look.
  3. System tokens such as --block-size-32 define the building blocks of layouts and color scales.
  4. System attributes such as ui-raised or ui-block-size="48" apply consistent spacing and contrasts.
  5. System classes for Colors, layouts, fonts are useful for component developers - they can fine-tune which class combinations for a component.
  6. Component style modules will override any system classes or attributes. Here is how to make components respect the user's theme, color-mode and accessibility presets:
  7. Custom style attributes override any rule. Note that you cannot use @container queries for responsive layouts here.

The system utilities defined in layers 3-5 are available as TypeScript objects for smoother dx.

All system utilities are customizable. Create a theme by overriding the rules [WIP]. Stick to the system utilities where possible to make your HTML or Vue template adapt to user preferences automatically.

Cross-browser sanitization

For cross-browser consistency, we use https://csstools.github.io/sanitize.css/ as the base layer. Check the comments in fw.css to see which version is pinned currently. We update the sanitize layer only once every few years - this part of CSS moves slowly.

The sanitize layer has lowest precedence: You can override all affected styles by adding rules to the document or system layers, or through component <style module>s.

Colors

accentSolid
blueDeepSolid
blueSolid
defaultSolid
destructiveSolid
greenDeepSolid
greenSolid
ochreDeepSolid
ochreSolid
pinkDeepSolid
pinkSolid
primarySolid
purpleDeepSolid
purpleSolid
redDeepSolid
redSolid
yellowDeepSolid
yellowSolid

Pick a color

class="color-yellow"
class="color-yellow-solid"

Some colors give you an inverted palette:

class="color-blue-deep-solid"
class="color-destructive-solid"

Override the contrast

Each color ranges from -50 (lowest contrast) to -950 (highest contrast). The difference between two numbers approximately corresponds to their contrast ratio: --color-foreground-900 on --color-background-450 will have a contrast ratio of approximately 4.5:1. For WCAG2.2 compliance, keep small text and symbols at 4.5:1 and large text and decoration at 3:1.

When you give an element a color class and a fill class (class="color-primary-solid"), the design system calculates three palettes for you: --color-background-*, --color-foreground and a generic --color.

style="color: var(--color-foreground-400)"style="color: var(--color-foreground-600)"style="color: var(--color-foreground-800)"
class="color-blue-solid"
class="color-blue-solid" style="background-color: var(--color-background-150)"

Preset background/foreground combos and interactive states

  • {color}-solid: Adds a solid background. For pastel colors, a subtler shade is used. For accents, a default shade is retained. For primary and dangerous palettes, the background is tinted while the foreground is neutral (default).
  • {color}-ghost: Like solid, except that the background is only activated on interaction (:focus, :hover).
  • {color}-deep-{solid or ghost}: A variation with negative color scales for deeper background shades.

For standard interactive elements (<a>, <button>, <select>), and elements with [ui-interactive], applying color-*-solid and color-*-ghost applIes appropriate colors to interactive states such as [aria-pressed="true"], :hover, :active.

All colors in fw.css
css
/**
* ====================================================================
* Color
* ====================================================================
*/

:root {
  color-scheme: light dark;
}

/**
* Basic funkwhale palette.
*
* When writing a theme, use your own theme-specific prefix: `--dracula-color-scary-900: chartreuse`.
* These series are extrapolated from original design system colors with the `generate_palette` python script,
* with minor adjustment in the extreme ranges.
*/
:root {
  /* blue */
  --fw-blue-50: oklch(1.070 0.000 206.19);
  --fw-blue-100: oklch(1.028 0.000 205.68);
  --fw-blue-150: oklch(0.986 0.000 206.78);
  --fw-blue-200: oklch(0.943 0.000 209.15);
  --fw-blue-250: oklch(0.900 0.012 212.43);
  --fw-blue-300: oklch(0.856 0.030 216.21);
  --fw-blue-350: oklch(0.810 0.048 220.03);
  --fw-blue-400: oklch(0.764 0.064 223.38);
  --fw-blue-450: oklch(0.717 0.075 225.69);
  --fw-blue-500: oklch(0.669 0.078 226.34);
  --fw-blue-550: oklch(0.619 0.078 226.32);
  --fw-blue-600: oklch(0.568 0.078 226.21);
  --fw-blue-650: oklch(0.514 0.076 225.94);
  --fw-blue-700: oklch(0.459 0.073 225.39);
  --fw-blue-750: oklch(0.400 0.069 224.42);
  --fw-blue-800: oklch(0.338 0.060 222.84);
  --fw-blue-850: oklch(0.271 0.048 220.31);
  --fw-blue-900: oklch(0.193 0.027 216.21);
  --fw-blue-950: oklch(0.080 0.000 207.35);
  /* gray */
  --fw-gray-50: oklch(1.070 0.002 -344.51);
  --fw-gray-100: oklch(1.028 0.000 211.88);
  --fw-gray-150: oklch(0.986 0.001 117.76);
  --fw-gray-200: oklch(0.943 0.004 17.22);
  --fw-gray-250: oklch(0.900 0.005 17.23);
  --fw-gray-300: oklch(0.856 0.006 17.25);
  --fw-gray-350: oklch(0.810 0.008 17.29);
  --fw-gray-400: oklch(0.764 0.008 17.33);
  --fw-gray-450: oklch(0.717 0.009 17.37);
  --fw-gray-500: oklch(0.669 0.010 17.40);
  --fw-gray-550: oklch(0.619 0.010 17.42);
  --fw-gray-600: oklch(0.568 0.010 17.43);
  --fw-gray-650: oklch(0.514 0.009 17.44);
  --fw-gray-700: oklch(0.459 0.009 17.46);
  --fw-gray-750: oklch(0.400 0.008 17.47);
  --fw-gray-800: oklch(0.338 0.007 17.48);
  --fw-gray-850: oklch(0.271 0.006 17.48);
  --fw-gray-900: oklch(0.193 0.003 17.48);
  --fw-gray-950: oklch(0.080 0.000 17.46);
  /* orange */
  --fw-orange-50: oklch(1.070 0.000 61.86);
  --fw-orange-100: oklch(1.028 0.000 58.35);
  --fw-orange-150: oklch(0.986 0.000 55.09);
  --fw-orange-200: oklch(0.943 0.000 52.10);
  --fw-orange-250: oklch(0.900 0.000 49.42);
  --fw-orange-300: oklch(0.856 0.039 47.05);
  --fw-orange-350: oklch(0.810 0.090 45.03);
  --fw-orange-400: oklch(0.764 0.133 43.38);
  --fw-orange-450: oklch(0.717 0.165 42.16);
  --fw-orange-500: oklch(0.669 0.185 41.39);
  --fw-orange-550: oklch(0.619 0.192 41.12);
  --fw-orange-600: oklch(0.568 0.181 41.68);
  --fw-orange-650: oklch(0.514 0.154 42.97);
  --fw-orange-700: oklch(0.459 0.122 44.39);
  --fw-orange-750: oklch(0.400 0.099 45.25);
  --fw-orange-800: oklch(0.338 0.083 45.77);
  --fw-orange-850: oklch(0.271 0.069 46.14);
  --fw-orange-900: oklch(0.193 0.059 46.10);
  --fw-orange-950: oklch(0.080 0.064 44.62);
  /* pastel-blue */
  --fw-pastel-blue-50: oklch(1.070 0.003 165.08);
  --fw-pastel-blue-100: oklch(1.028 0.000 171.15);
  --fw-pastel-blue-150: oklch(0.986 0.000 184.67);
  --fw-pastel-blue-200: oklch(0.943 0.001 196.92);
  --fw-pastel-blue-250: oklch(0.900 0.010 205.02);
  --fw-pastel-blue-300: oklch(0.856 0.029 212.26);
  --fw-pastel-blue-350: oklch(0.810 0.049 217.57);
  --fw-pastel-blue-400: oklch(0.764 0.060 219.73);
  --fw-pastel-blue-450: oklch(0.717 0.062 219.49);
  --fw-pastel-blue-500: oklch(0.669 0.064 218.85);
  --fw-pastel-blue-550: oklch(0.619 0.065 218.01);
  --fw-pastel-blue-600: oklch(0.568 0.064 217.22);
  --fw-pastel-blue-650: oklch(0.514 0.057 216.46);
  --fw-pastel-blue-700: oklch(0.459 0.047 215.46);
  --fw-pastel-blue-750: oklch(0.400 0.038 213.77);
  --fw-pastel-blue-800: oklch(0.338 0.029 210.07);
  --fw-pastel-blue-850: oklch(0.271 0.020 204.00);
  --fw-pastel-blue-900: oklch(0.193 0.009 195.31);
  --fw-pastel-blue-950: oklch(0.080 0.000 181.10);
  /* pastel-green */
  --fw-pastel-green-50: oklch(1.070 0.000 225.45);
  --fw-pastel-green-100: oklch(1.028 0.000 202.89);
  --fw-pastel-green-150: oklch(0.986 0.005 168.26);
  --fw-pastel-green-200: oklch(0.943 0.022 134.68);
  --fw-pastel-green-250: oklch(0.900 0.040 116.56);
  --fw-pastel-green-300: oklch(0.856 0.064 116.19);
  --fw-pastel-green-350: oklch(0.810 0.094 116.39);
  --fw-pastel-green-400: oklch(0.764 0.115 116.70);
  --fw-pastel-green-450: oklch(0.717 0.115 117.61);
  --fw-pastel-green-500: oklch(0.669 0.109 120.12);
  --fw-pastel-green-550: oklch(0.619 0.101 122.71);
  --fw-pastel-green-600: oklch(0.568 0.092 123.97);
  --fw-pastel-green-650: oklch(0.514 0.080 124.95);
  --fw-pastel-green-700: oklch(0.459 0.066 125.78);
  --fw-pastel-green-750: oklch(0.400 0.053 126.36);
  --fw-pastel-green-800: oklch(0.338 0.043 126.50);
  --fw-pastel-green-850: oklch(0.271 0.035 124.53);
  --fw-pastel-green-900: oklch(0.193 0.027 119.33);
  --fw-pastel-green-950: oklch(0.080 0.021 108.06);
  /* pastel-ochre */
  --fw-pastel-ochre-50: oklch(1.070 0.000 61.80);
  --fw-pastel-ochre-100: oklch(1.028 0.000 60.89);
  --fw-pastel-ochre-150: oklch(0.986 0.000 60.14);
  --fw-pastel-ochre-200: oklch(0.943 0.000 59.52);
  --fw-pastel-ochre-250: oklch(0.900 0.002 59.03);
  --fw-pastel-ochre-300: oklch(0.856 0.016 58.66);
  --fw-pastel-ochre-350: oklch(0.810 0.029 58.40);
  --fw-pastel-ochre-400: oklch(0.764 0.041 58.22);
  --fw-pastel-ochre-450: oklch(0.717 0.050 58.12);
  --fw-pastel-ochre-500: oklch(0.669 0.057 58.08);
  --fw-pastel-ochre-550: oklch(0.619 0.061 58.07);
  --fw-pastel-ochre-600: oklch(0.568 0.061 58.10);
  --fw-pastel-ochre-650: oklch(0.514 0.059 58.30);
  --fw-pastel-ochre-700: oklch(0.459 0.055 58.66);
  --fw-pastel-ochre-750: oklch(0.400 0.048 59.20);
  --fw-pastel-ochre-800: oklch(0.338 0.037 59.91);
  --fw-pastel-ochre-850: oklch(0.271 0.020 60.81);
  --fw-pastel-ochre-900: oklch(0.193 0.000 61.96);
  --fw-pastel-ochre-950: oklch(0.080 0.000 63.72);
  /* pastel-purple */
  --fw-pastel-purple-50: oklch(1.070 0.052 329.13);
  --fw-pastel-purple-100: oklch(1.028 0.040 329.71);
  --fw-pastel-purple-150: oklch(0.986 0.030 326.80);
  --fw-pastel-purple-200: oklch(0.943 0.025 323.47);
  --fw-pastel-purple-250: oklch(0.900 0.025 322.66);
  --fw-pastel-purple-300: oklch(0.856 0.035 322.68);
  --fw-pastel-purple-350: oklch(0.810 0.053 322.72);
  --fw-pastel-purple-400: oklch(0.764 0.073 322.76);
  --fw-pastel-purple-450: oklch(0.717 0.092 322.80);
  --fw-pastel-purple-500: oklch(0.669 0.102 322.82);
  --fw-pastel-purple-550: oklch(0.619 0.100 322.80);
  --fw-pastel-purple-600: oklch(0.568 0.089 322.72);
  --fw-pastel-purple-650: oklch(0.514 0.074 322.60);
  --fw-pastel-purple-700: oklch(0.459 0.061 322.46);
  --fw-pastel-purple-750: oklch(0.400 0.050 322.27);
  --fw-pastel-purple-800: oklch(0.338 0.040 321.99);
  --fw-pastel-purple-850: oklch(0.271 0.029 321.62);
  --fw-pastel-purple-900: oklch(0.193 0.017 321.11);
  --fw-pastel-purple-950: oklch(0.080 0.003 320.25);
  /* pastel-red */
  --fw-pastel-red-50: oklch(1.070 0.025 18.05);
  --fw-pastel-red-100: oklch(1.028 0.023 17.72);
  --fw-pastel-red-150: oklch(0.986 0.022 17.57);
  --fw-pastel-red-200: oklch(0.943 0.023 17.56);
  --fw-pastel-red-250: oklch(0.900 0.033 17.79);
  --fw-pastel-red-300: oklch(0.856 0.048 18.13);
  --fw-pastel-red-350: oklch(0.810 0.067 18.63);
  --fw-pastel-red-400: oklch(0.764 0.090 19.55);
  --fw-pastel-red-450: oklch(0.717 0.120 21.00);
  --fw-pastel-red-500: oklch(0.669 0.149 22.64);
  --fw-pastel-red-550: oklch(0.619 0.172 24.06);
  --fw-pastel-red-600: oklch(0.568 0.179 24.78);
  --fw-pastel-red-650: oklch(0.514 0.170 25.06);
  --fw-pastel-red-700: oklch(0.459 0.152 25.27);
  --fw-pastel-red-750: oklch(0.400 0.132 25.50);
  --fw-pastel-red-800: oklch(0.338 0.109 25.79);
  --fw-pastel-red-850: oklch(0.271 0.079 26.11);
  --fw-pastel-red-900: oklch(0.193 0.039 26.49);
  --fw-pastel-red-950: oklch(0.080 0.000 27.05);
  /* pastel-yellow */
  --fw-pastel-yellow-50: oklch(1.070 0.028 82.74);
  --fw-pastel-yellow-100: oklch(1.028 0.101 83.83);
  --fw-pastel-yellow-150: oklch(0.986 0.130 84.90);
  --fw-pastel-yellow-200: oklch(0.943 0.129 85.88);
  --fw-pastel-yellow-250: oklch(0.900 0.111 86.68);
  --fw-pastel-yellow-300: oklch(0.856 0.091 87.18);
  --fw-pastel-yellow-350: oklch(0.810 0.085 87.26);
  --fw-pastel-yellow-400: oklch(0.764 0.089 86.55);
  --fw-pastel-yellow-450: oklch(0.717 0.096 85.18);
  --fw-pastel-yellow-500: oklch(0.669 0.105 83.49);
  --fw-pastel-yellow-550: oklch(0.619 0.114 81.86);
  --fw-pastel-yellow-600: oklch(0.568 0.120 80.74);
  --fw-pastel-yellow-650: oklch(0.514 0.121 80.55);
  --fw-pastel-yellow-700: oklch(0.459 0.115 80.81);
  --fw-pastel-yellow-750: oklch(0.400 0.104 81.43);
  --fw-pastel-yellow-800: oklch(0.338 0.086 82.57);
  --fw-pastel-yellow-850: oklch(0.271 0.062 84.44);
  --fw-pastel-yellow-900: oklch(0.193 0.029 87.51);
  --fw-pastel-yellow-950: oklch(0.080 0.000 94.12);
  /* red */
  --fw-red-50: oklch(1.070 0.007 23.64);
  --fw-red-100: oklch(1.028 0.005 24.21);
  --fw-red-150: oklch(0.986 0.007 24.79);
  --fw-red-200: oklch(0.943 0.013 25.37);
  --fw-red-250: oklch(0.900 0.023 25.96);
  --fw-red-300: oklch(0.856 0.035 26.55);
  --fw-red-350: oklch(0.810 0.051 27.15);
  --fw-red-400: oklch(0.764 0.075 27.83);
  --fw-red-450: oklch(0.717 0.109 28.60);
  --fw-red-500: oklch(0.669 0.146 29.36);
  --fw-red-550: oklch(0.619 0.179 29.99);
  --fw-red-600: oklch(0.568 0.196 30.36);
  --fw-red-650: oklch(0.514 0.197 30.45);
  --fw-red-700: oklch(0.459 0.200 30.35);
  --fw-red-750: oklch(0.400 0.210 29.78);
  --fw-red-800: oklch(0.338 0.230 28.64);
  --fw-red-850: oklch(0.271 0.263 26.80);
  --fw-red-900: oklch(0.193 0.313 23.97);
  --fw-red-950: oklch(0.080 0.407 18.51);
  /* pink */
  --fw-pink-50: oklch(1.070 0.164 378.03);
  --fw-pink-100: oklch(1.028 0.177 376.46);
  --fw-pink-150: oklch(0.986 0.191 374.86);
  --fw-pink-200: oklch(0.943 0.205 373.24);
  --fw-pink-250: oklch(0.900 0.219 371.59);
  --fw-pink-300: oklch(0.856 0.233 369.91);
  --fw-pink-350: oklch(0.810 0.248 368.19);
  --fw-pink-400: oklch(0.764 0.263 366.45);
  --fw-pink-450: oklch(0.717 0.278 364.66);
  --fw-pink-500: oklch(0.669 0.293 362.82);
  --fw-pink-550: oklch(0.619 0.309 360.94);
  --fw-pink-600: oklch(0.568 0.326 358.99);
  --fw-pink-650: oklch(0.514 0.343 356.98);
  --fw-pink-700: oklch(0.459 0.361 354.87);
  --fw-pink-750: oklch(0.400 0.380 352.66);
  --fw-pink-800: oklch(0.338 0.400 350.30);
  --fw-pink-850: oklch(0.271 0.422 347.74);
  --fw-pink-900: oklch(0.193 0.446 344.81);
  --fw-pink-950: oklch(0.080 0.483 340.52);
}

/**
* Apply default light/dark theme colors
*/
@layer document {
  body {
    color: var(--color-700);
    background-color: var(--color-100);
  }
  :root {
      background-color: var(--color-100);
  }
}

/**
* Apply theme-specific colors to semantic situations.
*
* Use in components or styles (Example has a contrast ratio of ca. 4.5:1):
* ```css
*  background: var(--color-50);
*  color: var(--color-500);
* ```
*
* The number represents the approximate contrast against a white or black background.
* @reference Use `light-dark` to target both light and dark backgrounds at once: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/light-dark
* * `--color-*-100` is suitable as a background color.
* * `--color-*-400` has a contrast of approx. 3:1 over a background of 100, and is therefore usable for outlines and other essential non-text content according to WCAG2.
* * `--color-*-550+` has suitable contrast for high-visibility items such as small text.
* * If you use `--color-background-200` as a background color, then add 100 to all contrasting colors.
*
* On a dark background, higher numbers are brighter.
*/
@layer system {
  /* Named default scale - available regardless of class */
  :root {
    --color-default-50: light-dark(var(--fw-gray-50), var(--fw-gray-950));
    --color-default-100: light-dark(var(--fw-gray-100), var(--fw-gray-900));
    --color-default-150: light-dark(var(--fw-gray-150), var(--fw-gray-850));
    --color-default-200: light-dark(var(--fw-gray-200), var(--fw-gray-800));
    --color-default-250: light-dark(var(--fw-gray-250), var(--fw-gray-750));
    --color-default-300: light-dark(var(--fw-gray-300), var(--fw-gray-700));
    --color-default-350: light-dark(var(--fw-gray-350), var(--fw-gray-650));
    --color-default-400: light-dark(var(--fw-gray-400), var(--fw-gray-600));
    --color-default-450: light-dark(var(--fw-gray-450), var(--fw-gray-550));
    --color-default-500: light-dark(var(--fw-gray-500), var(--fw-gray-500));
    --color-default-550: light-dark(var(--fw-gray-550), var(--fw-gray-450));
    --color-default-600: light-dark(var(--fw-gray-600), var(--fw-gray-400));
    --color-default-650: light-dark(var(--fw-gray-650), var(--fw-gray-350));
    --color-default-700: light-dark(var(--fw-gray-700), var(--fw-gray-300));
    --color-default-750: light-dark(var(--fw-gray-750), var(--fw-gray-250));
    --color-default-800: light-dark(var(--fw-gray-800), var(--fw-gray-200));
    --color-default-850: light-dark(var(--fw-gray-850), var(--fw-gray-150));
    --color-default-900: light-dark(var(--fw-gray-900), var(--fw-gray-100));
    --color-default-950: light-dark(var(--fw-gray-950), var(--fw-gray-50));

    --color-shadow: light-dark(rgba(0,0,0,.3), rgba(0,0,0,.8));
  }

  /* Default color values */
  :root,
  .color-default, .color-default-solid, .color-default-ghost {
    --color-50: var(--color-default-50);
    --color-100: var(--color-default-100);
    --color-150: var(--color-default-150);
    --color-200: var(--color-default-200);
    --color-250: var(--color-default-250);
    --color-300: var(--color-default-300);
    --color-350: var(--color-default-350);
    --color-400: var(--color-default-400);
    --color-450: var(--color-default-450);
    --color-500: var(--color-default-500);
    --color-550: var(--color-default-550);
    --color-600: var(--color-default-600);
    --color-650: var(--color-default-650);
    --color-700: var(--color-default-700);
    --color-750: var(--color-default-750);
    --color-800: var(--color-default-800);
    --color-850: var(--color-default-850);
    --color-900: var(--color-default-900);
    --color-950: var(--color-default-950);

    --color-background: var(--color-200);
    --color-foreground: var(--color-750);

    /* Copy button colors */
    --color-background-active: var(--color-300);
    --color-foreground-active: var(--color-850);
    --color-background-pressed: var(--color-600);
    --color-foreground-pressed: var(--color-50);

    /* Tag colors */
    /* Dropdown Menu hover color*/
    --color-background-hover: var(--color-250);
    --color-foreground-hover: var(--color-800);

    --color-background-selected: light-dark(var(--color-850), var(--color-50));
    --color-foreground-selected: light-dark(var(--color-50), var(--color-850));

    --color-background-editing: light-dark(var(--fw-orange-500), var(--fw-orange-500));
    --color-foreground-editing: light-dark(var(--fw-orange-150), var(--fw-orange-50));
    /*---*/

    --color-link: light-dark(var(--fw-pastel-blue-550), var(--fw-pastel-blue-450));
    --color-link-hover: light-dark(var(--fw-pastel-blue-600), var(--fw-pastel-blue-400));
    --color-link-active: light-dark(var(--fw-pastel-blue-650), var(--fw-pastel-blue-350));

    --color-focus: light-dark(var(--fw-blue-400), var(--fw-blue-600));
    /*End TODO*/

    /* Set foreground shade */
    color: var(--color-foreground-850);

    /* Set background shade for solids and ghosts */
    &[class*='solid']{
      background-color: var(--color-background-200);
      &[ui-sunken] {
              background-color: var(--color-150);
          }
        &[ui-raised] {
            background-color: var(--color-300);
        }
    }

    /* Set varied shades for interactive states */
    &:is(a, button, select, [ui-interactive]) {
        &:hover, &:focus-visible {
            color: var(--color-foreground-750);
            background-color: var(--color-background-250);
            &[ui-raised] {
                background-color: var(--color-400);
            }

            &:active:hover, &:focus-visible:hover, &:focus-visible:active {
                color: var(--color-foreground-750);
                background-color: var(--color-background-350);
            }
        }
        &:is([aria-pressed="true"], [aria-expanded="true"], [aria-selected="true"], [aria-checked="true"], [aria-current]:not([aria-current="false"]):not([aria-current="page"])) {
            color: var(--color-foreground-50);
            background-color: var(--color-background-600);

            &:hover:not(:active){
                color: var(--color-foreground-150);
                background-color: var(--color-background-750);
            }
        }

        &[aria-current="page"] {
            color: var(--color-foreground-900);
            background-color: var(--color-background-300);
        }
    }
  }

  :is(.color-destructive, .color-primary, .color-accent) {
    --color-background: light-dark(var(--color-550), var(--color-350));
    --color-foreground: light-dark(var(--color-150), var(--color-900));

    --color-background-hover: light-dark(var(--color-600), var(--color-400));
    --color-foreground-hover: light-dark(var(--color-150), var(--color-950));

    --color-background-active: light-dark(var(--color-650), var(--color-300));
    --color-foreground-active: light-dark(var(--color-200), var(--color-800));

    --color-background-pressed: light-dark(var(--color-950), var(--color-50));
    --color-foreground-pressed: light-dark(var(--color-400), var(--color-600));
  }

  /* Accent */
  .color-accent, .color-accent-solid, .color-accent-ghost {
    --color-50: light-dark(var(--fw-orange-50), var(--fw-orange-950));
    --color-100: light-dark(var(--fw-orange-100), var(--fw-orange-900));
    --color-150: light-dark(var(--fw-orange-150), var(--fw-orange-850));
    --color-200: light-dark(var(--fw-orange-200), var(--fw-orange-800));
    --color-250: light-dark(var(--fw-orange-250), var(--fw-orange-750));
    --color-300: light-dark(var(--fw-orange-300), var(--fw-orange-700));
    --color-350: light-dark(var(--fw-orange-350), var(--fw-orange-650));
    --color-400: light-dark(var(--fw-orange-400), var(--fw-orange-600));
    --color-450: light-dark(var(--fw-orange-450), var(--fw-orange-550));
    --color-500: light-dark(var(--fw-orange-500), var(--fw-orange-500));
    --color-550: light-dark(var(--fw-orange-550), var(--fw-orange-450));
    --color-600: light-dark(var(--fw-orange-600), var(--fw-orange-400));
    --color-650: light-dark(var(--fw-orange-650), var(--fw-orange-350));
    --color-700: light-dark(var(--fw-orange-700), var(--fw-orange-300));
    --color-750: light-dark(var(--fw-orange-750), var(--fw-orange-250));
    --color-800: light-dark(var(--fw-orange-800), var(--fw-orange-200));
    --color-850: light-dark(var(--fw-orange-850), var(--fw-orange-150));
    --color-900: light-dark(var(--fw-orange-900), var(--fw-orange-100));
    --color-950: light-dark(var(--fw-orange-950), var(--fw-orange-50));

    /* Set foreground shade */
    color: var(--color-foreground-500);

    /* Set background shade for solids and ghosts */
    &[class*='solid'], &[class*='ghost'] {
        background-color: var(--color-background-200);
    }

    /* Set varied shades for interactive states */
    &:is(a, button, select, [ui-interactive]) {
        &:hover, &:focus-visible {
            color: var(--color-foreground-650);
            background-color: var(--color-background-450);

            &:focus-visible {
                color: var(--color-foreground-800);
                background-color: var(--color-background-400);
            }

            &:active:hover, &:focus-visible:hover, &:focus-visible:active {
                color: var(--color-foreground-750);
                background-color: var(--color-background-300);
            }

        }
            &:is([aria-pressed=true], [aria-expanded=true], [aria-selected=true], [aria-checked=true], [aria-current]:not([aria-current=false])):is([class*='ghost']) {
                color: var(--color-foreground-50);
                background-color: var(--color-background-600);

                &:hover:not(:active){
                    color: var(--color-foreground-150);
                    background-color: var(--color-background-750);
                }
            }
    }
  }

  /* Blue */
  .color-blue, .color-blue-solid, .color-blue-ghost, .color-blue-deep-solid, .color-blue-deep-ghost {
    --color-50: light-dark(var(--fw-pastel-blue-50), var(--fw-pastel-blue-950));
    --color-100: light-dark(var(--fw-pastel-blue-100), var(--fw-pastel-blue-900));
    --color-150: light-dark(var(--fw-pastel-blue-150), var(--fw-pastel-blue-850));
    --color-200: light-dark(var(--fw-pastel-blue-200), var(--fw-pastel-blue-800));
    --color-250: light-dark(var(--fw-pastel-blue-250), var(--fw-pastel-blue-750));
    --color-300: light-dark(var(--fw-pastel-blue-300), var(--fw-pastel-blue-700));
    --color-350: light-dark(var(--fw-pastel-blue-350), var(--fw-pastel-blue-650));
    --color-400: light-dark(var(--fw-pastel-blue-400), var(--fw-pastel-blue-600));
    --color-450: light-dark(var(--fw-pastel-blue-450), var(--fw-pastel-blue-550));
    --color-500: light-dark(var(--fw-pastel-blue-500), var(--fw-pastel-blue-500));
    --color-550: light-dark(var(--fw-pastel-blue-550), var(--fw-pastel-blue-450));
    --color-600: light-dark(var(--fw-pastel-blue-600), var(--fw-pastel-blue-400));
    --color-650: light-dark(var(--fw-pastel-blue-650), var(--fw-pastel-blue-350));
    --color-700: light-dark(var(--fw-pastel-blue-700), var(--fw-pastel-blue-300));
    --color-750: light-dark(var(--fw-pastel-blue-750), var(--fw-pastel-blue-250));
    --color-800: light-dark(var(--fw-pastel-blue-800), var(--fw-pastel-blue-200));
    --color-850: light-dark(var(--fw-pastel-blue-850), var(--fw-pastel-blue-150));
    --color-900: light-dark(var(--fw-pastel-blue-900), var(--fw-pastel-blue-100));
    --color-950: light-dark(var(--fw-pastel-blue-950), var(--fw-pastel-blue-50));

    /* Set foreground shade */
    color: var(--color-foreground-650);

    &[class*='deep'] {
        color: var(--color-foreground-900);
    }

    /* Set background shade for solids and ghosts */
    &[class*='solid'], &[class*='ghost'] {
        background-color: light-dark(var(--color-background-250), var(--color-background-200));
        &[ui-sunken] {
                background-color: var(--color-150);
            }
        &[ui-raised] {
            background-color: var(--color-350);
        }

        &[class*='deep'] {
            background-color: var(--color-background-600);
        }
    }

    /* Set varied shades for interactive states */
    &:is(a, button, select, [ui-interactive]) {
        &:hover, &:focus-visible {
            color: var(--color-foreground-650);
            background-color: var(--color-background-250);
            &[ui-sunken] {
                background-color: var(--color-150);
            }
            &[ui-raised] {
                background-color: var(--color-450);
            }

            &:focus-visible {
                color: var(--color-foreground-800);
                background-color: var(--color-background-400);
            }

            &:active:hover, &:focus-visible:hover, &:focus-visible:active {
                color: var(--color-foreground-750);
                background-color: var(--color-background-300);
            }

        }
            &:is([aria-pressed=true], [aria-expanded=true], [aria-selected=true], [aria-checked=true], [aria-current]:not([aria-current=false])) {
                color: var(--color-foreground-50);
                background-color: var(--color-background-600);

                &:hover:not(:active){
                    color: var(--color-foreground-150);
                    background-color: var(--color-background-750);
                }
            }
    }
  }

  .color-destructive, .color-destructive-solid, .color-destructive-ghost {
    --color-50: light-dark(var(--fw-red-50), var(--fw-red-950));
    --color-100: light-dark(var(--fw-red-100), var(--fw-red-900));
    --color-150: light-dark(var(--fw-red-150), var(--fw-red-850));
    --color-200: light-dark(var(--fw-red-200), var(--fw-red-800));
    --color-250: light-dark(var(--fw-red-250), var(--fw-red-750));
    --color-300: light-dark(var(--fw-red-300), var(--fw-red-700));
    --color-350: light-dark(var(--fw-red-350), var(--fw-red-650));
    --color-400: light-dark(var(--fw-red-400), var(--fw-red-600));
    --color-450: light-dark(var(--fw-red-450), var(--fw-red-550));
    --color-500: light-dark(var(--fw-red-500), var(--fw-red-500));
    --color-550: light-dark(var(--fw-red-550), var(--fw-red-450));
    --color-600: light-dark(var(--fw-red-600), var(--fw-red-400));
    --color-650: light-dark(var(--fw-red-650), var(--fw-red-350));
    --color-700: light-dark(var(--fw-red-700), var(--fw-red-300));
    --color-750: light-dark(var(--fw-red-750), var(--fw-red-250));
    --color-800: light-dark(var(--fw-red-800), var(--fw-red-200));
    --color-850: light-dark(var(--fw-red-850), var(--fw-red-150));
    --color-900: light-dark(var(--fw-red-900), var(--fw-red-100));
    --color-950: light-dark(var(--fw-red-950), var(--fw-red-50));

    --color-link: light-dark(var(--fw-pastel-yellow-50), var(--fw-pastel-yellow-950));
    --color-link-hover: light-dark(var(--fw-pastel-yellow-100), var(--fw-pastel-yellow-900));
    --color-link-active: light-dark(var(--fw-pastel-yellow-50), var(--fw-pastel-yellow-950));

    /* Set foreground shade */
    color: var(--color-foreground-650);

    /* Set background shade for solids and ghosts */
    &[class*='ghost'] {
        background-color: var(--color-background-500);
        color: light-dark(var(--color-foreground-550), var(--color-foreground-500));
    }

    &[class*='solid'] {
        background-color: light-dark(var(--color-background-500), var(--color-background-650));
        color: light-dark(var(--color-foreground-850), var(--color-foreground-200));
    }

    /* Set varied shades for interactive states */
    &:is(a, button, select, [ui-interactive]) {
        &:hover, &:focus-visible {
            color: var(--color-foreground-50);
            background-color: var(--color-background-600);

            &:active:hover, &:focus-visible:hover, &:focus-visible:active {
                color: var(--color-foreground-750);
                background-color: var(--color-background-350);
            }

        }
            &:is([aria-pressed=true], [aria-expanded=true], [aria-selected=true], [aria-checked=true], [aria-current]:not([aria-current=false])) {
                color: var(--color-foreground-50);
                background-color: var(--color-background-600);

                &:hover:not(:active){
                    color: var(--color-foreground-150);
                    background-color: var(--color-background-750);
                }
            }
    }
  }

  /* Green */
  .color-green, .color-green-deep-solid, .color-green-deep-ghost, .color-green-solid, .color-green-ghost {
    --color-50: light-dark(var(--fw-pastel-green-50), var(--fw-pastel-green-950));
    --color-100: light-dark(var(--fw-pastel-green-100), var(--fw-pastel-green-900));
    --color-150: light-dark(var(--fw-pastel-green-150), var(--fw-pastel-green-850));
    --color-200: light-dark(var(--fw-pastel-green-200), var(--fw-pastel-green-800));
    --color-250: light-dark(var(--fw-pastel-green-250), var(--fw-pastel-green-750));
    --color-300: light-dark(var(--fw-pastel-green-300), var(--fw-pastel-green-700));
    --color-350: light-dark(var(--fw-pastel-green-350), var(--fw-pastel-green-650));
    --color-400: light-dark(var(--fw-pastel-green-400), var(--fw-pastel-green-600));
    --color-450: light-dark(var(--fw-pastel-green-450), var(--fw-pastel-green-550));
    --color-500: light-dark(var(--fw-pastel-green-500), var(--fw-pastel-green-500));
    --color-550: light-dark(var(--fw-pastel-green-550), var(--fw-pastel-green-450));
    --color-600: light-dark(var(--fw-pastel-green-600), var(--fw-pastel-green-400));
    --color-650: light-dark(var(--fw-pastel-green-650), var(--fw-pastel-green-350));
    --color-700: light-dark(var(--fw-pastel-green-700), var(--fw-pastel-green-300));
    --color-750: light-dark(var(--fw-pastel-green-750), var(--fw-pastel-green-250));
    --color-800: light-dark(var(--fw-pastel-green-800), var(--fw-pastel-green-200));
    --color-850: light-dark(var(--fw-pastel-green-850), var(--fw-pastel-green-150));
    --color-900: light-dark(var(--fw-pastel-green-900), var(--fw-pastel-green-100));
    --color-950: light-dark(var(--fw-pastel-green-950), var(--fw-pastel-green-50));

    /* Set foreground shade */
    color: var(--color-foreground-700);

    &[class*='deep'] {
        color: var(--color-foreground-900);
    }

    /* Set background shade for solids and ghosts */
    &[class*='solid'], &[class*='ghost'] {
        background-color: var(--color-background-200);

        &[class*='deep'] {
            background-color: var(--color-background-500);
        }
    }

    /* Set varied shades for interactive states */
    &:is(a, button, select, [ui-interactive]) {
        &:hover, &:focus-visible {
            color: var(--color-foreground-650);
            background-color: var(--color-background-450);

            &:focus-visible {
                color: var(--color-foreground-800);
                background-color: var(--color-background-400);
            }

            &:active:hover, &:focus-visible:hover, &:focus-visible:active {
                color: var(--color-foreground-750);
                background-color: var(--color-background-300);
            }

        }
            &:is([aria-pressed=true], [aria-expanded=true], [aria-selected=true], [aria-checked=true], [aria-current]:not([aria-current=false])) {
                color: var(--color-foreground-50);
                background-color: var(--color-background-600);

                &:hover:not(:active){
                    color: var(--color-foreground-150);
                    background-color: var(--color-background-750);
                }
            }
    }
  }

  /* Ochre (reddish gray) */
  .color-ochre, .color-ochre-deep-ghost, .color-ochre-deep-solid, .color-ochre-solid, .color-ochre-ghost {
    --color-50: light-dark(var(--fw-pastel-ochre-50), var(--fw-pastel-ochre-950));
    --color-100: light-dark(var(--fw-pastel-ochre-100), var(--fw-pastel-ochre-900));
    --color-150: light-dark(var(--fw-pastel-ochre-150), var(--fw-pastel-ochre-850));
    --color-200: light-dark(var(--fw-pastel-ochre-200), var(--fw-pastel-ochre-800));
    --color-250: light-dark(var(--fw-pastel-ochre-250), var(--fw-pastel-ochre-750));
    --color-300: light-dark(var(--fw-pastel-ochre-300), var(--fw-pastel-ochre-700));
    --color-350: light-dark(var(--fw-pastel-ochre-350), var(--fw-pastel-ochre-650));
    --color-400: light-dark(var(--fw-pastel-ochre-400), var(--fw-pastel-ochre-600));
    --color-450: light-dark(var(--fw-pastel-ochre-450), var(--fw-pastel-ochre-550));
    --color-500: light-dark(var(--fw-pastel-ochre-500), var(--fw-pastel-ochre-500));
    --color-550: light-dark(var(--fw-pastel-ochre-550), var(--fw-pastel-ochre-450));
    --color-600: light-dark(var(--fw-pastel-ochre-600), var(--fw-pastel-ochre-400));
    --color-650: light-dark(var(--fw-pastel-ochre-650), var(--fw-pastel-ochre-350));
    --color-700: light-dark(var(--fw-pastel-ochre-700), var(--fw-pastel-ochre-300));
    --color-750: light-dark(var(--fw-pastel-ochre-750), var(--fw-pastel-ochre-250));
    --color-800: light-dark(var(--fw-pastel-ochre-800), var(--fw-pastel-ochre-200));
    --color-850: light-dark(var(--fw-pastel-ochre-850), var(--fw-pastel-ochre-150));
    --color-900: light-dark(var(--fw-pastel-ochre-900), var(--fw-pastel-ochre-100));
    --color-950: light-dark(var(--fw-pastel-ochre-950), var(--fw-pastel-ochre-50));

    /* Set foreground shade */
    color: var(--color-foreground-700);

    &[class*='deep'] {
        color: var(--color-foreground-950);
    }

    /* Set background shade for solids and ghosts */
    &[class*='solid'], &[class*='ghost'] {
        background-color: var(--color-background-250);

        &[class*='deep'] {
            background-color: light-dark(var(--color-background-550), var(--color-background-350));
            color: var(--color-150);
        }
    }

    /* Set varied shades for interactive states */
    &:is(a, button, select, [ui-interactive]) {
        &:hover, &:focus-visible {
            color: var(--color-foreground-650);
            background-color: var(--color-background-450);

            &:focus-visible {
                color: var(--color-foreground-800);
                background-color: var(--color-background-400);
            }

            &:active:hover, &:focus-visible:hover, &:focus-visible:active {
                color: var(--color-foreground-750);
                background-color: var(--color-background-300);
            }

        }
            &:is([aria-pressed=true], [aria-expanded=true], [aria-selected=true], [aria-checked=true], [aria-current]:not([aria-current=false])) {
                color: var(--color-foreground-50);
                background-color: var(--color-background-600);

                &:hover:not(:active){
                    color: var(--color-foreground-150);
                    background-color: var(--color-background-750);
                }
            }
    }
  }

  /* Primary */
  .color-primary, .color-primary-solid, .color-primary-ghost {
    --color-50: light-dark(var(--fw-blue-50), var(--fw-blue-950));
    --color-100: light-dark(var(--fw-blue-100), var(--fw-blue-900));
    --color-150: light-dark(var(--fw-blue-150), var(--fw-blue-850));
    --color-200: light-dark(var(--fw-blue-200), var(--fw-blue-800));
    --color-250: light-dark(var(--fw-blue-250), var(--fw-blue-750));
    --color-300: light-dark(var(--fw-blue-300), var(--fw-blue-700));
    --color-350: light-dark(var(--fw-blue-350), var(--fw-blue-650));
    --color-400: light-dark(var(--fw-blue-400), var(--fw-blue-600));
    --color-450: light-dark(var(--fw-blue-450), var(--fw-blue-550));
    --color-500: light-dark(var(--fw-blue-500), var(--fw-blue-500));
    --color-550: light-dark(var(--fw-blue-550), var(--fw-blue-450));
    --color-600: light-dark(var(--fw-blue-600), var(--fw-blue-400));
    --color-650: light-dark(var(--fw-blue-650), var(--fw-blue-350));
    --color-700: light-dark(var(--fw-blue-700), var(--fw-blue-300));
    --color-750: light-dark(var(--fw-blue-750), var(--fw-blue-250));
    --color-800: light-dark(var(--fw-blue-800), var(--fw-blue-200));
    --color-850: light-dark(var(--fw-blue-850), var(--fw-blue-150));
    --color-900: light-dark(var(--fw-blue-900), var(--fw-blue-100));
    --color-950: light-dark(var(--fw-blue-950), var(--fw-blue-50));

    --color-link: light-dark(var(--fw-blue-50), var(--fw-blue-950));
    --color-link-hover: light-dark(var(--fw-blue-100), var(--fw-blue-900));
    --color-link-active: light-dark(var(--fw-blue-50), var(--fw-blue-950));

    --color-focus: light-dark(var(--fw-blue-450), var(--fw-blue-550));

    /* Set foreground shade */
    color: var(--color-foreground-550);

    /* Set background shade for solids and ghosts. Note that background-color is transparent when not .ghost:hover */
    &[class*='solid'], &[class*='ghost']:hover{
        background-color: light-dark(var(--color-background-500), var(--color-background-650));
        &[ui-sunken] {
                background-color: var(--color-150);
            }

        color: light-dark(var(--color-foreground-850), var(--color-foreground-100));

    }

    /* Set varied shades for interactive states */
    &:is(a, button, select, [ui-interactive]) {
        &:is(:hover, :focus-visible) {
          color: var(--color-foreground-750);
        }
        &:is([class*='solid'], [class*='ghost']):is(:hover, :focus-visible) {
            color: light-dark(var(--color-foreground-750), var(--color-foreground-150));
            background-color: light-dark(var(--color-background-400), var(--color-background-550));
            &[ui-sunken] {
                background-color: var(--color-150);
            }
            &[ui-raised] {
                background-color: light-dark(var(--color-background-550), var(--color-background-600));  ;
            }

            &:active:hover, &:focus-visible:hover, &:focus-visible:active {
                color: var(--color-foreground-750);
                background-color: var(--color-background-350);
            }

        }
            &:is([aria-pressed=true], [aria-expanded=true], [aria-selected=true], [aria-checked=true], [aria-current]:not([aria-current=false])) {
                color: var(--color-foreground-50);
                background-color: var(--color-background-600);

                &:hover:not(:active){
                    color: var(--color-foreground-150);
                    background-color: var(--color-background-750);
                }
            }
    }
    }

    /* pink */
    .color-pink, .color-pink-deep-solid, .color-pink-deep-ghost, .color-pink-solid, .color-pink-ghost {
        --color-50: light-dark(var(--fw-pink-50), var(--fw-pink-950));
        --color-100: light-dark(var(--fw-pink-100), var(--fw-pink-900));
        --color-150: light-dark(var(--fw-pink-150), var(--fw-pink-850));
        --color-200: light-dark(var(--fw-pink-200), var(--fw-pink-800));
        --color-250: light-dark(var(--fw-pink-250), var(--fw-pink-750));
        --color-300: light-dark(var(--fw-pink-300), var(--fw-pink-700));
        --color-350: light-dark(var(--fw-pink-350), var(--fw-pink-650));
        --color-400: light-dark(var(--fw-pink-400), var(--fw-pink-600));
        --color-450: light-dark(var(--fw-pink-450), var(--fw-pink-550));
        --color-500: light-dark(var(--fw-pink-500), var(--fw-pink-500));
        --color-550: light-dark(var(--fw-pink-550), var(--fw-pink-450));
        --color-600: light-dark(var(--fw-pink-600), var(--fw-pink-400));
        --color-650: light-dark(var(--fw-pink-650), var(--fw-pink-350));
        --color-700: light-dark(var(--fw-pink-700), var(--fw-pink-300));
        --color-750: light-dark(var(--fw-pink-750), var(--fw-pink-250));
        --color-800: light-dark(var(--fw-pink-800), var(--fw-pink-200));
        --color-850: light-dark(var(--fw-pink-850), var(--fw-pink-150));
        --color-900: light-dark(var(--fw-pink-900), var(--fw-pink-100));
        --color-950: light-dark(var(--fw-pink-950), var(--fw-pink-50));

        /* Set foreground shade */
        color: var(--color-foreground-700);

        &[class*='deep'] {
            color: var(--color-foreground-950);
        }

        /* Set background shade for solids and ghosts */
        &[class*='solid'], &[class*='ghost'] {
            background-color: var(--color-background-200);

            &[class*='deep'] {
                background-color: light-dark(var(--color-background-550), var(--color-background-350));
                color: var(--color-150);
            }
        }

        /* Set varied shades for interactive states */
        &:is(a, button, select, [ui-interactive]) {
            &:hover, &:focus-visible {
                color: var(--color-foreground-650);
                background-color: var(--color-background-450);

                &:focus-visible {
                    color: var(--color-foreground-800);
                    background-color: var(--color-background-400);
                }

                &:active:hover, &:focus-visible:hover, &:focus-visible:active {
                    color: var(--color-foreground-750);
                    background-color: var(--color-background-300);
                }

            }
                &:is([aria-pressed=true], [aria-expanded=true], [aria-selected=true], [aria-checked=true], [aria-current]:not([aria-current=false])) {
                    color: var(--color-foreground-50);
                    background-color: var(--color-background-600);

                    &:hover:not(:active){
                        color: var(--color-foreground-150);
                        background-color: var(--color-background-750);
                    }
                }
        }
  }

  /*Correction: Primary foreground on dark background has higher contrast*/
    :is([class*="-deep-"], [class*="color-primary"], [class*="color-destructive"]) :is([class*="color-primary"]) {
      color: var(--color-foreground-800);
  }

  /* Purple */
  .color-purple, .color-purple-deep-solid, .color-purple-deep-ghost, .color-purple-solid, .color-purple-ghost {
    --color-50: light-dark(var(--fw-pastel-purple-50), var(--fw-pastel-purple-950));
    --color-100: light-dark(var(--fw-pastel-purple-100), var(--fw-pastel-purple-900));
    --color-150: light-dark(var(--fw-pastel-purple-150), var(--fw-pastel-purple-850));
    --color-200: light-dark(var(--fw-pastel-purple-200), var(--fw-pastel-purple-800));
    --color-250: light-dark(var(--fw-pastel-purple-250), var(--fw-pastel-purple-750));
    --color-300: light-dark(var(--fw-pastel-purple-300), var(--fw-pastel-purple-700));
    --color-350: light-dark(var(--fw-pastel-purple-350), var(--fw-pastel-purple-650));
    --color-400: light-dark(var(--fw-pastel-purple-400), var(--fw-pastel-purple-600));
    --color-450: light-dark(var(--fw-pastel-purple-450), var(--fw-pastel-purple-550));
    --color-500: light-dark(var(--fw-pastel-purple-500), var(--fw-pastel-purple-500));
    --color-550: light-dark(var(--fw-pastel-purple-550), var(--fw-pastel-purple-450));
    --color-600: light-dark(var(--fw-pastel-purple-600), var(--fw-pastel-purple-400));
    --color-650: light-dark(var(--fw-pastel-purple-650), var(--fw-pastel-purple-350));
    --color-700: light-dark(var(--fw-pastel-purple-700), var(--fw-pastel-purple-300));
    --color-750: light-dark(var(--fw-pastel-purple-750), var(--fw-pastel-purple-250));
    --color-800: light-dark(var(--fw-pastel-purple-800), var(--fw-pastel-purple-200));
    --color-850: light-dark(var(--fw-pastel-purple-850), var(--fw-pastel-purple-150));
    --color-900: light-dark(var(--fw-pastel-purple-900), var(--fw-pastel-purple-100));
    --color-950: light-dark(var(--fw-pastel-purple-950), var(--fw-pastel-purple-50));

    /* Set foreground shade */
    color: var(--color-foreground-700);

    &[class*='deep'] {
        color: var(--color-foreground-900);
    }

    /* Set background shade for solids and ghosts */
    &[class*='solid'], &[class*='ghost'] {
        background-color: var(--color-background-200);

        &[class*='deep'] {
            background-color: var(--color-background-500);
        }
    }

    /* Set varied shades for interactive states */
    &:is(a, button, select, [ui-interactive]) {
        &:hover, &:focus-visible {
            color: var(--color-foreground-650);
            background-color: var(--color-background-450);

            &:focus-visible {
                color: var(--color-foreground-800);
                background-color: var(--color-background-400);
            }

            &:active:hover, &:focus-visible:hover, &:focus-visible:active {
                color: var(--color-foreground-750);
                background-color: var(--color-background-300);
            }

        }
            &:is([aria-pressed=true], [aria-expanded=true], [aria-selected=true], [aria-checked=true], [aria-current]:not([aria-current=false])) {
                color: var(--color-foreground-50);
                background-color: var(--color-background-600);

                &:hover:not(:active){
                    color: var(--color-foreground-150);
                    background-color: var(--color-background-750);
                }
            }
    }
  }

  /* Red */
  .color-red, .color-red-deep-solid, .color-red-deep-ghost, .color-red-solid, .color-red-ghost {
    --color-50: light-dark(var(--fw-pastel-red-50), var(--fw-pastel-red-950));
    --color-100: light-dark(var(--fw-pastel-red-100), var(--fw-pastel-red-900));
    --color-150: light-dark(var(--fw-pastel-red-150), var(--fw-pastel-red-850));
    --color-200: light-dark(var(--fw-pastel-red-200), var(--fw-pastel-red-800));
    --color-250: light-dark(var(--fw-pastel-red-250), var(--fw-pastel-red-750));
    --color-300: light-dark(var(--fw-pastel-red-300), var(--fw-pastel-red-700));
    --color-350: light-dark(var(--fw-pastel-red-350), var(--fw-pastel-red-650));
    --color-400: light-dark(var(--fw-pastel-red-400), var(--fw-pastel-red-600));
    --color-450: light-dark(var(--fw-pastel-red-450), var(--fw-pastel-red-550));
    --color-500: light-dark(var(--fw-pastel-red-500), var(--fw-pastel-red-500));
    --color-550: light-dark(var(--fw-pastel-red-550), var(--fw-pastel-red-450));
    --color-600: light-dark(var(--fw-pastel-red-600), var(--fw-pastel-red-400));
    --color-650: light-dark(var(--fw-pastel-red-650), var(--fw-pastel-red-350));
    --color-700: light-dark(var(--fw-pastel-red-700), var(--fw-pastel-red-300));
    --color-750: light-dark(var(--fw-pastel-red-750), var(--fw-pastel-red-250));
    --color-800: light-dark(var(--fw-pastel-red-800), var(--fw-pastel-red-200));
    --color-850: light-dark(var(--fw-pastel-red-850), var(--fw-pastel-red-150));
    --color-900: light-dark(var(--fw-pastel-red-900), var(--fw-pastel-red-100));
    --color-950: light-dark(var(--fw-pastel-red-950), var(--fw-pastel-red-50));

    /* Set foreground shade */
    color: var(--color-foreground-800);

    &[class*='deep'] {
        color: var(--color-foreground-950);
    }

    /* Set background shade for solids and ghosts */
    &[class*='solid'], &[class*='ghost'] {
        background-color: var(--color-background-200);

        &[class*='deep'] {
            background-color: var(--color-background-600);
        }
    }

    /* Set varied shades for interactive states */
    &:is(a, button, select, [ui-interactive]) {
        &:hover, &:focus-visible {
            color: var(--color-foreground-650);
            background-color: var(--color-background-450);

            &:focus-visible {
                color: var(--color-foreground-800);
                background-color: var(--color-background-400);
            }

            &:active:hover, &:focus-visible:hover, &:focus-visible:active {
                color: var(--color-foreground-750);
                background-color: var(--color-background-300);
            }

        }
            &:is([aria-pressed=true], [aria-expanded=true], [aria-selected=true], [aria-checked=true], [aria-current]:not([aria-current=false])) {
                color: var(--color-foreground-50);
                background-color: var(--color-background-600);

                &:hover:not(:active){
                    color: var(--color-foreground-150);
                    background-color: var(--color-background-750);
                }
            }
    }
  }

  /* Yellow */
  .color-yellow, .color-yellow-deep-solid, .color-yellow-deep-ghost, .color-yellow-solid, .color-yellow-ghost {
    --color-50: light-dark(var(--fw-pastel-yellow-50), var(--fw-pastel-yellow-950));
    --color-100: light-dark(var(--fw-pastel-yellow-100), var(--fw-pastel-yellow-900));
    --color-150: light-dark(var(--fw-pastel-yellow-150), var(--fw-pastel-yellow-850));
    --color-200: light-dark(var(--fw-pastel-yellow-200), var(--fw-pastel-yellow-800));
    --color-250: light-dark(var(--fw-pastel-yellow-250), var(--fw-pastel-yellow-750));
    --color-300: light-dark(var(--fw-pastel-yellow-300), var(--fw-pastel-yellow-700));
    --color-350: light-dark(var(--fw-pastel-yellow-350), var(--fw-pastel-yellow-650));
    --color-400: light-dark(var(--fw-pastel-yellow-400), var(--fw-pastel-yellow-600));
    --color-450: light-dark(var(--fw-pastel-yellow-450), var(--fw-pastel-yellow-550));
    --color-500: light-dark(var(--fw-pastel-yellow-500), var(--fw-pastel-yellow-500));
    --color-550: light-dark(var(--fw-pastel-yellow-550), var(--fw-pastel-yellow-450));
    --color-600: light-dark(var(--fw-pastel-yellow-600), var(--fw-pastel-yellow-400));
    --color-650: light-dark(var(--fw-pastel-yellow-650), var(--fw-pastel-yellow-350));
    --color-700: light-dark(var(--fw-pastel-yellow-700), var(--fw-pastel-yellow-300));
    --color-750: light-dark(var(--fw-pastel-yellow-750), var(--fw-pastel-yellow-250));
    --color-800: light-dark(var(--fw-pastel-yellow-800), var(--fw-pastel-yellow-200));
    --color-850: light-dark(var(--fw-pastel-yellow-850), var(--fw-pastel-yellow-150));
    --color-900: light-dark(var(--fw-pastel-yellow-900), var(--fw-pastel-yellow-100));
    --color-950: light-dark(var(--fw-pastel-yellow-950), var(--fw-pastel-yellow-50));

    /* Set foreground shade */
    color: var(--color-foreground-750);

    &[class*='deep'] {
        color: var(--color-foreground-950);
    }

    /* Set background shade for solids and ghosts */
    &[class*='solid'], &[class*='ghost'] {
        background-color: light-dark(var(--color-background-150), var(--color-background-300));

        &[class*='deep'] {
            background-color: light-dark(var(--color-background-600), var(--color-background-350));
        }
    }

    /* Set varied shades for interactive states */
    &:is(a, button, select, [ui-interactive]) {
        &:hover, &:focus-visible {
            color: var(--color-foreground-650);
            background-color: var(--color-background-450);

            &:focus-visible {
                color: var(--color-foreground-800);
                background-color: var(--color-background-400);
            }

            &:active:hover, &:focus-visible:hover, &:focus-visible:active {
                color: var(--color-foreground-750);
                background-color: var(--color-background-300);
            }
        }

        &:is([aria-pressed=true], [aria-expanded=true], [aria-selected=true], [aria-checked=true], [aria-current]:not([aria-current=false])) {
            color: var(--color-foreground-50);
            background-color: var(--color-background-600);

            &:hover:not(:active){
                color: var(--color-foreground-150);
                background-color: var(--color-background-750);
            }
        }
    }
  }
}

/* Decoration */
[ui-shadow] {
    box-shadow: 0px 2px 8px 0px var(--color-shadow);
}

/* Branding */
@layer document {
    /* eslint-disable-next-line css/use-baseline */
  :is(p, a, input, span, label, h1, h2, h3, h4, h5, h6, h7, h8, header, footer, article, button, code)::selection {
    background-color: color-mix(in oklch, transparent 75%, color-mix(in oklch, var(--color-link) 80%, currentColor));
  }
}


@layer system {
/* Note: Using "inherits:false" here. If we ever want to access the background-color in non-solids, we can replace it with an override-rule which sets it to transparent for each element that has a color with no solid fill. I prefer the "low-power" solution (less dangling values) of @property and I see no use case for reusing an ancestor's background color. */
@property --color-background-50 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-100 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-150 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-200 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-250 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-300 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-350 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-400 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-450 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-500 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-550 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-600 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-650 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-700 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-750 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-800 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-850 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-900 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}
@property --color-background-950 {
    syntax: '<color>';
    inherits: false;
    initial-value: transparent;
}

/* Filled surfaces with positive scale */
@scope (:is(
   /* Implicit default on :root */
   :root,

    /* Positive fill */
    .color-default-solid,
    .color-accent-solid,
    .color-green-solid,
    .color-blue-solid,
    .color-ochre-solid,
    .color-purple-solid,
    .color-red-solid,
    .color-yellow-solid,

    /* Positive ghost fill */
    :is(
        .color-default-ghost,
        .color-accent-ghost,
        .color-green-ghost,
        .color-blue-ghost,
        .color-ochre-ghost,
        .color-purple-ghost,
        .color-red-ghost,
        .color-yellow-ghost
    ):is(
        :hover,
        :focus-visible,
        [aria-selected=true],
        [aria-pressed=true],
        [aria-expanded=true],
        [aria-checked=true],
        [aria-current]:not([aria-current=false]))
)) {
    /* 1. Apply positive background to filled element */
    :scope {
    --color-background-50: var(--color-50);
    --color-background-100: var(--color-100);
    --color-background-150: var(--color-150);
    --color-background-200: var(--color-200);
    --color-background-250: var(--color-250);
    --color-background-300: var(--color-300);
    --color-background-350: var(--color-350);
    --color-background-400: var(--color-400);
    --color-background-450: var(--color-450);
    --color-background-500: var(--color-500);
    --color-background-550: var(--color-550);
    --color-background-600: var(--color-600);
    --color-background-650: var(--color-650);
    --color-background-700: var(--color-700);
    --color-background-750: var(--color-750);
    --color-background-800: var(--color-800);
    --color-background-850: var(--color-850);
    --color-background-900: var(--color-900);
    --color-background-950: var(--color-950);
    }

    /* 2. Apply positive foregrounds (inheriting) to all descendents with color-class down to next solid */
    :scope, [class*='color-'] {
    --color-foreground-50: var(--color-50);
    --color-foreground-100: var(--color-100);
    --color-foreground-150: var(--color-150);
    --color-foreground-200: var(--color-200);
    --color-foreground-250: var(--color-250);
    --color-foreground-300: var(--color-300);
    --color-foreground-350: var(--color-350);
    --color-foreground-400: var(--color-400);
    --color-foreground-450: var(--color-450);
    --color-foreground-500: var(--color-500);
    --color-foreground-550: var(--color-550);
    --color-foreground-600: var(--color-600);
    --color-foreground-650: var(--color-650);
    --color-foreground-700: var(--color-700);
    --color-foreground-750: var(--color-750);
    --color-foreground-800: var(--color-800);
    --color-foreground-850: var(--color-850);
    --color-foreground-900: var(--color-900);
    --color-foreground-950: var(--color-950);
    }

    /* 3. Exception: Solid accent has default background*/
    :scope[class*='color-accent'] {
        --color-background-50: var(--color-default-50);
        --color-background-100: var(--color-default-100);
        --color-background-150: var(--color-default-150);
        --color-background-200: var(--color-default-200);
        --color-background-250: var(--color-default-250);
        --color-background-300: var(--color-default-300);
        --color-background-350: var(--color-default-350);
        --color-background-400: var(--color-default-400);
        --color-background-450: var(--color-default-450);
        --color-background-500: var(--color-default-500);
        --color-background-550: var(--color-default-550);
        --color-background-600: var(--color-default-600);
        --color-background-650: var(--color-default-650);
        --color-background-700: var(--color-default-700);
        --color-background-750: var(--color-default-750);
        --color-background-800: var(--color-default-800);
        --color-background-850: var(--color-default-850);
        --color-background-900: var(--color-default-900);
        --color-background-950: var(--color-default-950);
    }
}

/* Filled surfaces with negative scale */
@scope (:is(
    /* Negative fill */
    .color-primary-solid,
    .color-destructive-solid,
    .color-green-deep-solid,
    .color-blue-deep-solid,
    .color-ochre-deep-solid,
    .color-purple-deep-solid,
    .color-red-deep-solid,
    .color-yellow-deep-solid,

    /* Negative ghost fill */
    :is(
        .color-primary-ghost,
        .color-destructive-ghost,
        .color-green-deep-ghost,
        .color-blue-deep-ghost,
        .color-ochre-deep-ghost,
        .color-purple-deep-ghost,
        .color-red-deep-ghost,
        .color-yellow-deep-ghost
    ):is(
        :hover,
        :focus-visible,
        [aria-selected=true],
        [aria-pressed=true],
        [aria-expanded=true],
        [aria-checked=true],
        [aria-current]:not([aria-current=false]))
)) {
    /* 1. Apply negative background to filled element */
    :scope {
    --color-background-50: var(--color-950);
    --color-background-100: var(--color-900);
    --color-background-150: var(--color-850);
    --color-background-200: var(--color-800);
    --color-background-250: var(--color-750);
    --color-background-300: var(--color-700);
    --color-background-350: var(--color-650);
    --color-background-400: var(--color-600);
    --color-background-450: var(--color-550);
    --color-background-500: var(--color-500);
    --color-background-550: var(--color-450);
    --color-background-600: var(--color-400);
    --color-background-650: var(--color-350);
    --color-background-700: var(--color-300);
    --color-background-750: var(--color-250);
    --color-background-800: var(--color-200);
    --color-background-850: var(--color-150);
    --color-background-900: var(--color-100);
    --color-background-950: var(--color-50);
    }

    /* 2. Apply negative foregrounds (inheriting) to all descendents with color-class down to next solid */
    :scope, [class*='color-'] {
    --color-foreground-50: var(--color-950);
    --color-foreground-100: var(--color-900);
    --color-foreground-150: var(--color-850);
    --color-foreground-200: var(--color-800);
    --color-foreground-250: var(--color-750);
    --color-foreground-300: var(--color-700);
    --color-foreground-350: var(--color-650);
    --color-foreground-400: var(--color-600);
    --color-foreground-450: var(--color-550);
    --color-foreground-500: var(--color-500);
    --color-foreground-550: var(--color-450);
    --color-foreground-600: var(--color-400);
    --color-foreground-650: var(--color-350);
    --color-foreground-700: var(--color-300);
    --color-foreground-750: var(--color-250);
    --color-foreground-800: var(--color-200);
    --color-foreground-850: var(--color-150);
    --color-foreground-900: var(--color-100);
    --color-foreground-950: var(--color-50);
    }

    /* 3. Exception: Solid primaries and destructive surfaces have default foreground*/
    :scope[class*='color-primary'], :scope[class*='color-destructive'] {
    --color-foreground-50: var(--color-default-950);
    --color-foreground-100: var(--color-default-900);
    --color-foreground-150: var(--color-default-850);
    --color-foreground-200: var(--color-default-800);
    --color-foreground-250: var(--color-default-750);
    --color-foreground-300: var(--color-default-700);
    --color-foreground-350: var(--color-default-650);
    --color-foreground-400: var(--color-default-600);
    --color-foreground-450: var(--color-default-550);
    --color-foreground-500: var(--color-default-500);
    --color-foreground-550: var(--color-default-450);
    --color-foreground-600: var(--color-default-400);
    --color-foreground-650: var(--color-default-350);
    --color-foreground-700: var(--color-default-300);
    --color-foreground-750: var(--color-default-250);
    --color-foreground-800: var(--color-default-200);
    --color-foreground-850: var(--color-default-150);
    --color-foreground-900: var(--color-default-100);
    --color-foreground-950: var(--color-default-50);
    }
  }
}

Responsive layouts

Use classes such as .layout-grid, .layout-row-reverse, .layout-stack etc. to create responsive layouts.

Tips

  • Add attributes such as align-start to elements with a layout class to align its items.
  • In nested flex layouts (row, stack), ui-shrinkable makes the child container shrink before the parent container does so.
  • Inside a grid, ui-span-all grows an item to full width.
All layouts in fw.css
css

/**
* ====================================================================
* Layout
* ====================================================================
*
* - Default font size
* - Sizes and gaps in both dimensions
* - Predefined flex and grid layouts
* - Utilities for flex and grid children
*/

@layer document {
  html,
  body {
    font-size: 16px;
  }
}

/**
* Block sizes
*
* Discrete values on continuous scales. They combine nicely to form rhythms.
* Components expose them as string props:
* `<Button ui-block-size=24>`
*
* For vertical rhythm, all block sizes are fractions or multiples of 48 (the base rhythm). When using non-integer fractions such as 40 (5/6), add a gap of 1/6 to restore the rhythm.
*
* * Scales are unitless. Their name indicates the base CSS implementation but theme authors can apply nonlinear mappings.
* * To forbid page authors from setting a certain scalar, component authors can use `ui-block-size: never`.
*/
:root {
  --block-size-4: 4px;
  --block-size-6: 6px;
  --block-size-8: 8px;
  --block-size-12: 12px;
  --block-size-16: 16px;
  --block-size-24: 24px;
  --block-size-32: 32px;
  --block-size-40: 40px;
  --block-size-48: 48px;
  --block-size-56: 56px;
  --block-size-64: 64px;
  --block-size-72: 72px;
  --block-size-96: 96px;
}

@layer system {
    /**
     * In LTR writing systems, define vertical and then horizontal spacing     between items. Only applies to grid and flex (row, stack) layouts.
    */
    [ui-gap] {
        gap: var(--block-gap) var(--inline-gap);
    }
}

/*
## Block cadences and gaps

A cadence is the block-size (height) at which a pattern repeats. Example: Consecutive labeled single-line inputs in a form repeat each 96px. Use the gradient of gaps and cadences to create visual hierarchy and groupings. Equal cadences can harmonize a layout. Add spacers where sections are a little smaller than the next **multiple of 32px**.

Gaps and cadences are measured between **optical lines**. The optical line of text goes through the `-` glyph in the Lato font, which is 0.36em above the baseline. The optical lines of rectangular shapes are simply the edges. The optical lines of round and triangular shapes add some [_overshoot_](#overshoot), typically 8%, to balance out optical weight.

- **96**: Cadence of labeled inputs in a form
- **64**:
  - Gap between shapes or sections on a page
  - Gap between sections in a form
  - Total height (first to last line) of a single-line input
- **32**:
  - Distance between cards in a grid
  - Line height in large text and heading
  - Gap between items in a section
- **24**:
  - Line height in paragraph text
  - Bounding box height of a small circular or rectangular button or a pill ("Token")
- **16**: Line height in small text
*/

@layer system {
    [ui-gap='0'] {
    --block-gap: 0;
    }
    /** **4**: Floating layouts inside Menu items or inputs */
    [ui-gap='4'] {
        --block-gap: var(--block-size-4);
    }
    /** **16**: Gap between label and component; between icon and text */
    [ui-gap='8'] {
        --block-gap: var(--block-size-8);
    }
    /** **12**: In Modal actions */
    [ui-gap='12'] {
        --block-gap: var(--block-size-12);
    }
    /** **16**: Toolbars with unlabeled items */
    [ui-gap='16'] {
        --block-gap: var(--block-size-16);
    }
  [ui-line-height='16'] {
    line-height: var(--block-size-16);
  }

  /* Line height in paragraph text: */
  [ui-line-height='24'] {
    line-height: var(--block-size-24);
  }

  /* Bounding box height of a small circular or rectangular button or a pill ("Token"): */
  [ui-block-size='24'] {
    block-size: var(--block-size-24);
    --block-size: var(--block-size-24);
  }

  /* Bounding box height of a dense button or a input: */
  [ui-block-size='40'] {
    block-size: var(--block-size-40);
    --block-size: var(--block-size-40);
  }

  [ui-line-height='32'] {
    line-height: var(--block-size-32);
  }

  /*Gap between items in a section:*/
  [ui-gap='32'] {
    --block-gap: var(--block-size-32);
    --inline-gap: var(--inline-size-32);
  }

  [ui-block-size='48'] {
    block-size: var(--block-size-48);
    --block-size: var(--block-size-48);
  }

  /* Total height (first to last line) of a single-line input: */
  [ui-block-size='64'] {
    block-size: var(--block-size-64);
  }


  /** **48 8**: Gap for dense forms and labeled toolbars (Higher block gap)*/
  [ui-gap='48 8'] {
      --block-gap: var(--block-size-48);
  }
  /** **48 16**: Typical gap for toolbars where items have labels*/
  [ui-gap='48 16'] {
      --block-gap: var(--block-size-48);
  }
  /** Gap between sections in a form; gap between shapes or sections on a page:*/
  [ui-gap='64 8'] {
      --block-gap: var(--block-size-64);
  }
  /** Cadence of labeled inputs in a form: */
  [ui-block-size='96'] {
    block-size: var(--block-size-96);
  }
}

/*
## Inline sizes
*/
:root {
  --inline-size-4: 4px;
  --inline-size-8: 8px;
  --inline-size-12: 12px;
  --inline-size-16: 16px;
  --inline-size-24: 24px;
  --inline-size-32: 32px;
  --inline-size-40: 40px;
  --inline-size-48: 48px;
  --inline-size-56: 56px;
  --inline-size-72: 72px;
  --inline-size-80: 80px;
  --inline-size-96: 96px;
  --inline-size-128: 128px;
  --inline-size-160: 160px;
  --inline-size-208: 208px;
  --inline-size-240: 240px;
  --inline-size-288: 288px;
  --inline-size-320: 320px;
  --inline-size-448: 448px;
}

/*
## Inline cadences and gaps

With a typical gap width of 32 and a column width of 48, we can construct a grid with the following typical widths:

- **8**: Typical gap for toolbars
- **24**: Bounding box of a small (circular or square) button
- **32**:
  - Typical gap for card grids
  - Cadence of small buttons
- **48**:
  - Width of 1 column
  - Width of a square button
  - Bounding box width of a circular or square button
- **56**:
  - Cadence of circular or square buttons
- **80**: Cadence of single-column cards
- **96**: Content width in a small card or button
- **128**:
  - Width of a 2-column (small) card
  - Width of a standard button
- **160**:
  - Cadence of 2-column cards
  - Content width in a medium card
- **208**: Width of 3-column (medium) card
- **240**:
  - Cadence of 3-column cards
  - Content width in a large card
- **288**:
  - Width of a 4-column (large) card
  - Width and content width of an activity (no padding)
- **320**: Cadence of 4-column cards
- **448**: 6 columns
*/

@layer system {
    [ui-gap='0'] {
    --inline-gap: 0;
    }
    /** **4**: Floating layouts inside Menu items or inputs */
    [ui-gap='4'] {
        --inline-gap: var(--inline-size-4);
    }
    /** Gap between sections in a form; gap between shapes or sections on a page */
    [ui-gap='64 8'] {
        --inline-gap: var(--inline-size-8);
    }
      /** **16**: Gap between label and component; between icon and text */
      [ui-gap='8'] {
          --inline-gap: var(--inline-size-8);
      }
      /** **12**: In Modal actions */
      [ui-gap='12'] {
          --inline-gap: var(--inline-size-12);
      }
      /** 16: Gap **/
      /** **16**: Toolbars with unlabeled items */
      [ui-gap='16'] {
          --inline-gap: var(--inline-size-16);
      }
      /* **48**: One column (without gutter) */
      [ui-inline-size='48'] {
        inline-size: var(--inline-size-48);
      }
      /** **48 8**: Gap for dense forms and labeled toolbars (Higher block gap)*/
      [ui-gap='48 8'] {
          --inline-gap: var(--inline-size-8);
      }
      /** **48 16**: Typical gap for toolbars where items have labels*/
      [ui-gap='48 16'] {
          --inline-gap: var(--inline-size-16);
      }
  /* **96**: Content in a small card or button */
  [ui-inline-size='96'] {
    inline-size: var(--inline-size-96);
  }
  /* **128**: Size of a small card or default button (shape-field) */
  [ui-inline-size='128'] {
    inline-size: var(--inline-size-128);
    grid-column: span 2;
  }
  /* **160**: Content in a wide (3-column) card */
  [ui-inline-size='160'] {
    inline-size: var(--inline-size-160);
  }
  /* **208**: Size of 3-column (medium) card */
  [ui-inline-size='208'] {
    inline-size: var(--inline-size-208);
    grid-column: span 3;
  }
  /* **240**: Cadence of 3-column cards;  Content width in a large card */
  [ui-inline-size='240'] {
    inline-size: var(--inline-size-240);
    grid-column: span 4;
  }
  /* **288**: Size (and content size) of an activity (no padding); 4-columns (large) card*/
  [ui-inline-size='288'] {
    inline-size: var(--inline-size-288);
    grid-column: span 4;
  }
  /* **448**: 6 column object */
  [ui-inline-size="448"] {
    inline-size: var(--inline-size-448);
    min-inline-size: var(--inline-size-448);
    grid-column: span 6;
  }
}

/*
* Container layouts
*
* Containers align, Items override. Prefer `align-items` over `align-self`.
* Use <Gap> component instead of `justify-content` to split flex items into separate groups with flexible gaps.
*/

@layer system {
  .layout-row, .layout-row-reverse {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    flex-direction: row;
  }

  [class*="layout"] {
      &.align-start {
        align-items: flex-start;
      }
      &.align-center {
        align-items: center;
      }
      &.align-stretch {
        align-items: stretch;
      }
      &.justify-start {
        justify-content: flex-start;
      }
      &.justify-center {
        justify-content: center;
      }
      &.justify-stretch {
        justify-content: stretch;
      }
      &.justify-end {
        justify-content: flex-end;
      }
  }

  .layout-row-reverse {
    flex-direction: row-reverse;
  }

  .layout-stack {
    display: flex;
    flex-direction: column;
    align-items: stretch;
  }

  .layout-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, 48px);
    gap: var(--block-size-24) var(--inline-size-32);
    grid-auto-flow: row dense;
  }

  /* Padding */

  .padding-small {
    --padding-inline-start: 16px;
    --padding-inline-end: 16px;
    --padding-block-start: 24px;
    --padding-block-end: 32px;
  }

  .padding-large {
    --padding-inline-start: 24px;
    --padding-inline-end: 24px;
    --padding-block-start: 32px;
    --padding-block-end: 48px;
  }

  .padding-page {
    --padding-inline-start: 32px;
    --padding-inline-end: 32px;
    --padding-block-start: 22px;
    --padding-block-end: 48px;
  }

  @media (max-width: 768px) {
    .padding-page {
      --padding-inline-start: 16px;
      --padding-inline-end: 16px;
    }
  }

  .padding-modal {
    --padding-inline-start: 32px;
    --padding-inline-end: 32px;
    --padding-block-start: 16px;
    --padding-block-end: 48px;
  }

  [class*="padding-"] {
    padding: var(--padding-block-start) var(--padding-inline-end) var(--padding-block-end) var(--padding-inline-start);
    > * {
        --bleed-inline-start: var(--padding-inline-start);
        --bleed-inline-end: var(--padding-inline-end);
        --bleed-block-start: var(--padding-block-start);
        --bleed-block-end: var(--padding-block-end);
      }
  }

  @scope ([class*="padding-"]) {
    [ui-bleed-inline],
    [ui-bleed] {
      margin-inline: calc(var(--bleed-inline-start) * -1) calc(var(--bleed-inline-end) * -1);
      padding-inline: var(--bleed-inline-start) var(--bleed-inline-end);
    }

    [ui-bleed-block],
    [ui-bleed] {
      margin-block: calc(var(--bleed-block-start) * -1) calc(var(--bleed-block-end) * -1);
      padding-block: var(--bleed-block-start) var(--bleed-block-end);
    }

  }

  /* Sizing */

  /** Add to container inside another container if it won't shrink */
  [ui-shrinkable] {
    min-inline-size: 0;
    flex: 1 1 0;
  }

  /** Keep in a single row/column */
  [ui-nowrap] {
      flex-wrap: nowrap;
      text-wrap: nowrap;
  }

  /** Stretch to fill grid */
  [ui-span-all] {
      grid-column: 1 / -1;
      &[ui-inline-size] {
          inline-size: auto;      }
  }

  /**
* ====================================================================
* Shape
* ====================================================================
*/

  /* Modal */
  .shape-modal {
    border-radius: 16px;
  }

  /* Card */
  .shape-card {
    border-radius: 12px;
    &:not([ui-inline-size]):not([ui-span-all]) {
        grid-column: span 3;
        inline-size: var(--inline-size-208);
    }
  }

  /* Form field */
  .shape-field {
    border-radius: max(calc(var(--block-size, 48px) / 2 - 16px), 8px);
    min-inline-size: max-content;
    &:not([ui-inline-size]) {
        inline-size: 128px;
    }
    padding: 0 16px;
  }

  /* Narrow form field */
  .shape-field-minimal {
    border-radius: max(calc(var(--block-size, 48px) / 2 - 16px), 8px);
    min-inline-size: max-content;
    inline-size: max-content;
    padding: 0 16px;
  }

  /* Growing form field */
  .shape-field-grow {
    border-radius: max(calc(var(--block-size, 48px) / 2 - 16px), 8px);
    min-inline-size: max-content;
    inline-size: 128px;
    padding: 0 16px;
    flex-grow: 1;
  }

  /* Full-width form field */
  .shape-field-full {
      border-radius: max(calc(var(--block-size, 48px) / 2 - 16px), 8px);
      inline-size: auto;
      min-inline-size: 0;
      --grid-column: 1 / -1;
      padding: 0 16px;
      place-self: stretch;
  }

  /* Pill */
  .shape-pill {
    border-radius: 100vmax;
    width: fit-content;

    &[ui-block-size]:not([ui-overshoot-none]) {
      margin-inline: calc(-0.08 * var(--block-size));
    }
  }

  /* Circle */
  .shape-circle {
    aspect-ratio: 1 / 1;
    border-radius: 100vmax;

    &:after {
      border-radius: 100vmax;
    }

    &[ui-block-size]:not([ui-overshoot-none]) {
      block-size: calc(1.16 * var(--block-size));
      inline-size: calc(1.16 * var(--block-size));
      margin: calc(-0.08 * var(--block-size));
    }
  }

  /* Square */
  .shape-square {
    border-radius: calc(var(--block-size, 48px) / 2 - 16px);
    aspect-ratio: 1 / 1;

    &[ui-block-size] {
      block-size: var(--block-size);
      inline-size: var(--block-size);
    }
  }
}

/* TODO: Remove after transition to CSS */
#VPContent .VPPage button {
  border-width: 0px;
}

System utilities

Where possible, use Web defaults such as aria-pressed or disabled to cover semantics and looks in one go. In addition, the funkwhale design system offers a set of purely visual utilities, including custom properties ("Tokens") such as -- color-background-250, attributes such as ui-indicate-overflow and classes such as shape-circle.

These utilities are fully themeable.

See generated/fw.ts for the full list of the tokens, attributes and classes available.
ts
/* Do not edit! Autogenerated from ~/ui/fw.css with ~/ui/fw.generate.ts */


import type { Trim } from "type-fest"

export type Color = 'color-accent' | 'color-accent-ghost' | 'color-accent-solid' | 'color-blue' | 'color-blue-deep-ghost' | 'color-blue-deep-solid' | 'color-blue-ghost' | 'color-blue-solid' | 'color-default' | 'color-default-ghost' | 'color-default-solid' | 'color-destructive' | 'color-destructive-ghost' | 'color-destructive-solid' | 'color-green' | 'color-green-deep-ghost' | 'color-green-deep-solid' | 'color-green-ghost' | 'color-green-solid' | 'color-ochre' | 'color-ochre-deep-ghost' | 'color-ochre-deep-solid' | 'color-ochre-ghost' | 'color-ochre-solid' | 'color-pink' | 'color-pink-deep-ghost' | 'color-pink-deep-solid' | 'color-pink-ghost' | 'color-pink-solid' | 'color-primary' | 'color-primary-ghost' | 'color-primary-solid' | 'color-purple' | 'color-purple-deep-ghost' | 'color-purple-deep-solid' | 'color-purple-ghost' | 'color-purple-solid' | 'color-red' | 'color-red-deep-ghost' | 'color-red-deep-solid' | 'color-red-ghost' | 'color-red-solid' | 'color-yellow' | 'color-yellow-deep-ghost' | 'color-yellow-deep-solid' | 'color-yellow-ghost' | 'color-yellow-solid'
export type Fill = boolean
export type Font = 'font-caption' | 'font-caption-emphasized' | 'font-card-title' | 'font-large-section-heading' | 'font-monospace' | 'font-page-heading' | 'font-radio' | 'font-secondary' | 'font-section-heading' | 'font-small' | 'font-subsection-heading' | 'font-title' | 'font-title-large'
export type Layout = 'layout-grid' | 'layout-row' | 'layout-row-reverse' | 'layout-stack'
export type Align = 'align-center' | 'align-start' | 'align-stretch'
export type Justify = 'justify-center' | 'justify-end' | 'justify-start' | 'justify-stretch'
export type Padding = 'padding-large' | 'padding-modal' | 'padding-page' | 'padding-small'
export type Shape = 'shape-card' | 'shape-circle' | 'shape-field' | 'shape-field-full' | 'shape-field-grow' | 'shape-field-minimal' | 'shape-modal' | 'shape-pill' | 'shape-square'
export type Variable = '--bleed-block-end' | '--bleed-block-start' | '--bleed-inline-end' | '--bleed-inline-start' | '--block-gap' | '--block-size' | '--block-size-12' | '--block-size-16' | '--block-size-20' | '--block-size-24' | '--block-size-32' | '--block-size-4' | '--block-size-40' | '--block-size-48' | '--block-size-64' | '--block-size-8' | '--block-size-96' | '--color-100' | '--color-150' | '--color-200' | '--color-250' | '--color-300' | '--color-350' | '--color-400' | '--color-450' | '--color-50' | '--color-500' | '--color-550' | '--color-600' | '--color-650' | '--color-700' | '--color-750' | '--color-800' | '--color-850' | '--color-900' | '--color-950' | '--color-background' | '--color-background-150' | '--color-background-200' | '--color-background-250' | '--color-background-300' | '--color-background-350' | '--color-background-400' | '--color-background-450' | '--color-background-50' | '--color-background-500' | '--color-background-550' | '--color-background-600' | '--color-background-650' | '--color-background-750' | '--color-background-active' | '--color-background-editing' | '--color-background-hover' | '--color-background-pressed' | '--color-background-selected' | '--color-default-100' | '--color-default-150' | '--color-default-200' | '--color-default-250' | '--color-default-300' | '--color-default-350' | '--color-default-400' | '--color-default-450' | '--color-default-50' | '--color-default-500' | '--color-default-550' | '--color-default-600' | '--color-default-650' | '--color-default-700' | '--color-default-750' | '--color-default-800' | '--color-default-850' | '--color-default-900' | '--color-default-950' | '--color-focus' | '--color-foreground' | '--color-foreground-100' | '--color-foreground-150' | '--color-foreground-200' | '--color-foreground-50' | '--color-foreground-500' | '--color-foreground-550' | '--color-foreground-650' | '--color-foreground-700' | '--color-foreground-750' | '--color-foreground-800' | '--color-foreground-850' | '--color-foreground-900' | '--color-foreground-950' | '--color-foreground-active' | '--color-foreground-editing' | '--color-foreground-hover' | '--color-foreground-pressed' | '--color-foreground-selected' | '--color-link' | '--color-link-active' | '--color-link-hover' | '--color-shadow' | '--fade-size' | '--fw-blue-100' | '--fw-blue-150' | '--fw-blue-200' | '--fw-blue-250' | '--fw-blue-300' | '--fw-blue-350' | '--fw-blue-400' | '--fw-blue-450' | '--fw-blue-50' | '--fw-blue-500' | '--fw-blue-550' | '--fw-blue-600' | '--fw-blue-650' | '--fw-blue-700' | '--fw-blue-750' | '--fw-blue-800' | '--fw-blue-850' | '--fw-blue-900' | '--fw-blue-950' | '--fw-gray-100' | '--fw-gray-150' | '--fw-gray-200' | '--fw-gray-250' | '--fw-gray-300' | '--fw-gray-350' | '--fw-gray-400' | '--fw-gray-450' | '--fw-gray-50' | '--fw-gray-500' | '--fw-gray-550' | '--fw-gray-600' | '--fw-gray-650' | '--fw-gray-700' | '--fw-gray-750' | '--fw-gray-800' | '--fw-gray-850' | '--fw-gray-900' | '--fw-gray-950' | '--fw-orange-100' | '--fw-orange-150' | '--fw-orange-200' | '--fw-orange-250' | '--fw-orange-300' | '--fw-orange-350' | '--fw-orange-400' | '--fw-orange-450' | '--fw-orange-50' | '--fw-orange-500' | '--fw-orange-550' | '--fw-orange-600' | '--fw-orange-650' | '--fw-orange-700' | '--fw-orange-750' | '--fw-orange-800' | '--fw-orange-850' | '--fw-orange-900' | '--fw-orange-950' | '--fw-pastel-blue-100' | '--fw-pastel-blue-150' | '--fw-pastel-blue-200' | '--fw-pastel-blue-250' | '--fw-pastel-blue-300' | '--fw-pastel-blue-350' | '--fw-pastel-blue-400' | '--fw-pastel-blue-450' | '--fw-pastel-blue-50' | '--fw-pastel-blue-500' | '--fw-pastel-blue-550' | '--fw-pastel-blue-600' | '--fw-pastel-blue-650' | '--fw-pastel-blue-700' | '--fw-pastel-blue-750' | '--fw-pastel-blue-800' | '--fw-pastel-blue-850' | '--fw-pastel-blue-900' | '--fw-pastel-blue-950' | '--fw-pastel-green-100' | '--fw-pastel-green-150' | '--fw-pastel-green-200' | '--fw-pastel-green-250' | '--fw-pastel-green-300' | '--fw-pastel-green-350' | '--fw-pastel-green-400' | '--fw-pastel-green-450' | '--fw-pastel-green-50' | '--fw-pastel-green-500' | '--fw-pastel-green-550' | '--fw-pastel-green-600' | '--fw-pastel-green-650' | '--fw-pastel-green-700' | '--fw-pastel-green-750' | '--fw-pastel-green-800' | '--fw-pastel-green-850' | '--fw-pastel-green-900' | '--fw-pastel-green-950' | '--fw-pastel-ochre-100' | '--fw-pastel-ochre-150' | '--fw-pastel-ochre-200' | '--fw-pastel-ochre-250' | '--fw-pastel-ochre-300' | '--fw-pastel-ochre-350' | '--fw-pastel-ochre-400' | '--fw-pastel-ochre-450' | '--fw-pastel-ochre-50' | '--fw-pastel-ochre-500' | '--fw-pastel-ochre-550' | '--fw-pastel-ochre-600' | '--fw-pastel-ochre-650' | '--fw-pastel-ochre-700' | '--fw-pastel-ochre-750' | '--fw-pastel-ochre-800' | '--fw-pastel-ochre-850' | '--fw-pastel-ochre-900' | '--fw-pastel-ochre-950' | '--fw-pastel-purple-100' | '--fw-pastel-purple-150' | '--fw-pastel-purple-200' | '--fw-pastel-purple-250' | '--fw-pastel-purple-300' | '--fw-pastel-purple-350' | '--fw-pastel-purple-400' | '--fw-pastel-purple-450' | '--fw-pastel-purple-50' | '--fw-pastel-purple-500' | '--fw-pastel-purple-550' | '--fw-pastel-purple-600' | '--fw-pastel-purple-650' | '--fw-pastel-purple-700' | '--fw-pastel-purple-750' | '--fw-pastel-purple-800' | '--fw-pastel-purple-850' | '--fw-pastel-purple-900' | '--fw-pastel-purple-950' | '--fw-pastel-red-100' | '--fw-pastel-red-150' | '--fw-pastel-red-200' | '--fw-pastel-red-250' | '--fw-pastel-red-300' | '--fw-pastel-red-350' | '--fw-pastel-red-400' | '--fw-pastel-red-450' | '--fw-pastel-red-50' | '--fw-pastel-red-500' | '--fw-pastel-red-550' | '--fw-pastel-red-600' | '--fw-pastel-red-650' | '--fw-pastel-red-700' | '--fw-pastel-red-750' | '--fw-pastel-red-800' | '--fw-pastel-red-850' | '--fw-pastel-red-900' | '--fw-pastel-red-950' | '--fw-pastel-yellow-100' | '--fw-pastel-yellow-150' | '--fw-pastel-yellow-200' | '--fw-pastel-yellow-250' | '--fw-pastel-yellow-300' | '--fw-pastel-yellow-350' | '--fw-pastel-yellow-400' | '--fw-pastel-yellow-450' | '--fw-pastel-yellow-50' | '--fw-pastel-yellow-500' | '--fw-pastel-yellow-550' | '--fw-pastel-yellow-600' | '--fw-pastel-yellow-650' | '--fw-pastel-yellow-700' | '--fw-pastel-yellow-750' | '--fw-pastel-yellow-800' | '--fw-pastel-yellow-850' | '--fw-pastel-yellow-900' | '--fw-pastel-yellow-950' | '--fw-pink-100' | '--fw-pink-150' | '--fw-pink-200' | '--fw-pink-250' | '--fw-pink-300' | '--fw-pink-350' | '--fw-pink-400' | '--fw-pink-450' | '--fw-pink-50' | '--fw-pink-500' | '--fw-pink-550' | '--fw-pink-600' | '--fw-pink-650' | '--fw-pink-700' | '--fw-pink-750' | '--fw-pink-800' | '--fw-pink-850' | '--fw-pink-900' | '--fw-pink-950' | '--fw-red-100' | '--fw-red-150' | '--fw-red-200' | '--fw-red-250' | '--fw-red-300' | '--fw-red-350' | '--fw-red-400' | '--fw-red-450' | '--fw-red-50' | '--fw-red-500' | '--fw-red-550' | '--fw-red-600' | '--fw-red-650' | '--fw-red-700' | '--fw-red-750' | '--fw-red-800' | '--fw-red-850' | '--fw-red-900' | '--fw-red-950' | '--grid-column' | '--inline-gap' | '--inline-size-12' | '--inline-size-128' | '--inline-size-16' | '--inline-size-160' | '--inline-size-208' | '--inline-size-240' | '--inline-size-288' | '--inline-size-32' | '--inline-size-4' | '--inline-size-448' | '--inline-size-48' | '--inline-size-8' | '--inline-size-96' | '--mask-end' | '--mask-padding-inline-end' | '--mask-padding-inline-start' | '--mask-start' | '--padding-block-end' | '--padding-block-start' | '--padding-inline-end' | '--padding-inline-start'
export type Attribute = 'ui-bleed' | 'ui-bleed-block' | 'ui-bleed-inline' | 'ui-block-size' | 'ui-gap' | 'ui-indicate-overflow' | 'ui-inline-size' | 'ui-interactive' | 'ui-line-height' | 'ui-nowrap' | 'ui-overshoot-none' | 'ui-raised' | 'ui-shrinkable' | 'ui-span-all' | 'ui-sunken'

export const fw = {
  color: {
    accent: 'color-accent',
    accentGhost: 'color-accent-ghost',
    accentSolid: 'color-accent-solid',
    blue: 'color-blue',
    blueDeepGhost: 'color-blue-deep-ghost',
    blueDeepSolid: 'color-blue-deep-solid',
    blueGhost: 'color-blue-ghost',
    blueSolid: 'color-blue-solid',
    default: 'color-default',
    defaultGhost: 'color-default-ghost',
    defaultSolid: 'color-default-solid',
    destructive: 'color-destructive',
    destructiveGhost: 'color-destructive-ghost',
    destructiveSolid: 'color-destructive-solid',
    green: 'color-green',
    greenDeepGhost: 'color-green-deep-ghost',
    greenDeepSolid: 'color-green-deep-solid',
    greenGhost: 'color-green-ghost',
    greenSolid: 'color-green-solid',
    ochre: 'color-ochre',
    ochreDeepGhost: 'color-ochre-deep-ghost',
    ochreDeepSolid: 'color-ochre-deep-solid',
    ochreGhost: 'color-ochre-ghost',
    ochreSolid: 'color-ochre-solid',
    pink: 'color-pink',
    pinkDeepGhost: 'color-pink-deep-ghost',
    pinkDeepSolid: 'color-pink-deep-solid',
    pinkGhost: 'color-pink-ghost',
    pinkSolid: 'color-pink-solid',
    primary: 'color-primary',
    primaryGhost: 'color-primary-ghost',
    primarySolid: 'color-primary-solid',
    purple: 'color-purple',
    purpleDeepGhost: 'color-purple-deep-ghost',
    purpleDeepSolid: 'color-purple-deep-solid',
    purpleGhost: 'color-purple-ghost',
    purpleSolid: 'color-purple-solid',
    red: 'color-red',
    redDeepGhost: 'color-red-deep-ghost',
    redDeepSolid: 'color-red-deep-solid',
    redGhost: 'color-red-ghost',
    redSolid: 'color-red-solid',
    yellow: 'color-yellow',
    yellowDeepGhost: 'color-yellow-deep-ghost',
    yellowDeepSolid: 'color-yellow-deep-solid',
    yellowGhost: 'color-yellow-ghost',
    yellowSolid: 'color-yellow-solid'
  },
  font: {
    caption: 'font-caption',
    captionEmphasized: 'font-caption-emphasized',
    cardTitle: 'font-card-title',
    largeSectionHeading: 'font-large-section-heading',
    monospace: 'font-monospace',
    pageHeading: 'font-page-heading',
    radio: 'font-radio',
    secondary: 'font-secondary',
    sectionHeading: 'font-section-heading',
    small: 'font-small',
    subsectionHeading: 'font-subsection-heading',
    title: 'font-title',
    titleLarge: 'font-title-large'
  },
  layout: {
    grid: 'layout-grid',
    row: 'layout-row',
    rowReverse: 'layout-row-reverse',
    stack: 'layout-stack'
  },
  align: {
    center: 'align-center',
    start: 'align-start',
    stretch: 'align-stretch'
  },
  justify: {
    center: 'justify-center',
    end: 'justify-end',
    start: 'justify-start',
    stretch: 'justify-stretch'
  },
  padding: {
    large: 'padding-large',
    modal: 'padding-modal',
    page: 'padding-page',
    small: 'padding-small'
  },
  shape: {
    card: 'shape-card',
    circle: 'shape-circle',
    field: 'shape-field',
    fieldFull: 'shape-field-full',
    fieldGrow: 'shape-field-grow',
    fieldMinimal: 'shape-field-minimal',
    modal: 'shape-modal',
    pill: 'shape-pill',
    square: 'shape-square'
  }
} as const

export type Ui = {
  uiBleed: boolean
  uiBleedBlock: boolean
  uiBleedInline: boolean
  uiBlockSize: '24' | '40' | '48' | '64' | '96'
  uiGap: '0' | '4' | '8' | '12' | '16' | '32' | '48 8' | '48 16' | '64 8'
  uiIndicateOverflow: boolean
  uiInlineSize: '48' | '96' | '128' | '160' | '208' | '240' | '288' | '448'
  uiInteractive: boolean
  uiLineHeight: '16' | '24' | '32'
  uiNowrap: boolean
  uiOvershootNone: boolean
  uiRaised: boolean
  uiShrinkable: boolean
  uiSpanAll: boolean
  uiSunken: boolean
}

export const ui = {
  bleed: true,
  bleedBlock: true,
  bleedInline: true,
  blockSize: {
      '24': '24',
      '40': '40',
      '48': '48',
      '64': '64',
      '96': '96'
    },
  gap: {
      '0': '0',
      '4': '4',
      '8': '8',
      '12': '12',
      '16': '16',
      '32': '32',
      '48 8': '48 8',
      '48 16': '48 16',
      '64 8': '64 8'
    },
  indicateOverflow: true,
  inlineSize: {
      '48': '48',
      '96': '96',
      '128': '128',
      '160': '160',
      '208': '208',
      '240': '240',
      '288': '288',
      '448': '448'
    },
  interactive: true,
  lineHeight: {
      '16': '16',
      '24': '24',
      '32': '32'
    },
  nowrap: true,
  overshootNone: true,
  raised: true,
  shrinkable: true,
  spanAll: true,
  sunken: true
}

export type Fw = typeof fw;

/**
 * Make sure the class and ui attributes are defined in fw.css.
 *
 * @reference https://design.funkwhale.audio/#/workspace/582e7be1-0cc7-11ed-87a1-ae44a720651d/e3a00150-0f5e-11ed-adb9-fff9e854a67c?page-id=63cce0e0-1a5f-11ed-8551-a35b3c702efa
 */
export type WithClass<C extends {
    align? : Align
    color? : Color
    font? : Font
    layout? : Layout
    shape? : Shape
}, T extends Partial<Ui>>= {
  class?: EmptyIsUndefined<Trim<
  `${
      ToString<C['align']>
    }${
      ToString<C['color']>
    }${
      ToString<C['font']>
    }${
      ToString<C['layout']>
    }${
      ToString<C['shape']>
    }`
  >>
} & T

type ToString<T extends string | undefined> = T extends string ? `${T} ` : ''

type EmptyIsUndefined<T extends string> = T extends '' ? undefined : T

export default fw

Using system attributes

fw.css defines some attributes that can be reused across all components and HTML elements.

Example

Definition (fw.css -> generated/fw.ts)

ts
uiBlockSize: '24' | '40' | '48' | '64' | '96'

Usage (Vue component or HTML file)

template
<Button ui-block-size="40">

Prefer using attributes such as ui-sunken over custom classes or styles where possible - this allows themes to change the look of the UI consistently and may prevent accessibility barriers or visual glitches when using custom themes.

System attributes are automatically exposed as typescript objects for smoother dx. More about typed design system attributes

Using system classes

Funkwhale classes are simple, composable utilities. Following the precedent set by the Tailwind team, we encourage you to always write out fill class names such as color-default-ghost layout-stack shape-circle.

Use the WithAttributes type utility to fine-tune the classes and attributes available on your custom component.

Design system architecture

Design decisions

See Architecture for an overview of the patterns we use to keep the codebase maintainable. Our long-term goal is to keep the design system and component library close to standard Html and WAI-Aria recommendations, and to leverage declarative patterns to make the Vue or HTML code as explicit and expressive as possible.

  • The fw theme is based on the original designs on https://design.funkwhale.audio. Palettes are extended into whites and blacks for versatility.
  • Fill types (solid, ghost) are part of the color name so that they appear together in the final class string.
  • The color definitions contain some redundancy - each color rule defines all default shades for all interactive states. Developers can fine-tune each individual shade, and we avoid overridden and indirect rules at the cost of repetition. Do use multibuffer editing for overarching refactors.
  • No borders. Only outlines (see the Interactivity section).
  • Repetition over abstraction - we avoid overriding rules and calculating intermediate variables such as --color-background-pressed and instead make the rules very granular and the custom properties non-inheriting where necessary.