Code Monkey home page Code Monkey logo

terrazzo's Introduction

⛋ Terrazzo Monorepo

This repo serves as the home for:

  • Terrazzo CLI: generate code from DTCG tokens (formerly known as “Cobalt UI”)
  • Terrazzo Color Picker, a futuristic colorpicker that can handle wide gamut and high bit-depth colors in stunning color reproduction
  • Terrazzo Tiles DS: a design system for documenting design systems
  • Token Lab (coming soon): generate design systems from scratch, or start from an existing OSS design system

🔹 Cobalt UI

Cobalt UI 2.0 was renamed to Terrazzo (same project, same people). To see the code for Cobalt 1.0, see the 1.x branch.

terrazzo's People

Contributors

drwpow avatar github-actions[bot] avatar dev-nicolaos avatar mike-engel avatar nclsndr avatar citguy avatar autarc avatar agnelnieves avatar aitorllj93 avatar bheston avatar james-nash avatar sareg0 avatar r1m avatar

Stargazers

Abhishiv Saxena avatar James Dinnes avatar Stefano Faieta avatar Konstantin Denerz avatar Eva Decker avatar Richard McCartney avatar Marcel Körtgen avatar Max Kuhlmay avatar Nicolás Pérez avatar Daniel Sousa avatar  avatar Stanislav avatar Kevin Abou Hanna avatar James Lutley avatar TJ avatar  avatar Leigh Kendell avatar  avatar Petteri Tiilikka avatar Eduardo Diaz Ugarte avatar Filip Malinowski avatar Łukasz Wiśniewski avatar An Pham avatar Karim Semmoud avatar Denis Pankov avatar José Breijo avatar Eric Cheung avatar David Sklář avatar Chase McCoy avatar Raí Siqueira avatar Daniel Saunders avatar Jonny Gamba avatar Harry Tran avatar Andrew Brey avatar  avatar Oliver Barnwell avatar Allan Alexandre avatar Yadiel Vélez avatar Evgeny avatar Nate Moore avatar  avatar Markus Hsi-Yang Fritz avatar Caleb Jasik avatar João Palmeiro avatar Niko Laitinen avatar Dieter Luypaert avatar Michal Szymczak avatar Olivia G. avatar Zdorovtsev Viktor avatar Timur Guseynov avatar Scott Rod avatar Nick Tassone avatar Ryosuke avatar Busticated avatar Federico Kratter Thaler avatar Alexandre Stahmer avatar Mike Stecker avatar Sam Walsh avatar Mehdi avatar Erik Goens avatar David Leger avatar  avatar Sergi Luque avatar Dylan Frankland avatar Yanis avatar Peter Blinov avatar Joseph Curtis avatar Pavel Laptev avatar Viyan.UX Engineer avatar Eugene Lyulka avatar Wayne Shih avatar Salama Ashoush avatar Ed avatar Evgeniy Baranov avatar  avatar  avatar Janne Aukia avatar Michael Zumstein avatar Mykhaylo Ryechkin avatar Joshua Ferrell avatar Sean Kinread avatar Kipp Ashford avatar Andrejs Agejevs avatar Charles Harris avatar Jonathan van Wunnik avatar Selwyn avatar Bouba avatar Tobias Fried avatar Ricky avatar ehne avatar Olabode Emmanuel Leomanuel avatar Maxim Molochkov avatar mr rinima avatar Timur avatar  avatar Kristóf Poduszló avatar Stanislas Laurent avatar Azan avatar Luke Secomb avatar Graham Hoefer avatar

Watchers

 avatar Max Kuhlmay avatar Caleb Jasik avatar Kathleen McMahon avatar  avatar  avatar

terrazzo's Issues

Possible bug in typography-mixins output in plugin-sass

Thank you again for the quick fixes!

I think I found another one: When using plugin-sass in conjunction with plugin-css (CSS Variable Mode), the "default" mode of mixins will use CSS variables, while the following modes are still using the actual values. As I unserstand it, they too should be using the variables to be fully dynamic.

Here's a sample output as generated by the input below:

  "fontalias": (
    default: (
      "font-family": (var(--fontalias-font-family)),
      "font-size": (var(--fontalias-font-size)),
    ),
    "l": (
      "font-family": (Helvetica, -system-ui, sans-serif),
      "font-size": (2rem),
    ),
    "m": (
      "font-family": (Helvetica, -system-ui, sans-serif),
      "font-size": (1.5rem),
    ),
    "s": (
      "font-family": (Helvetica, -system-ui, sans-serif),
      "font-size": (1rem),
    ),
  ),

Here's the tokens.config.mjs ...

import pluginSass from '@cobalt-ui/plugin-sass';
/** @type {import('@cobalt-ui/core').Config} */

export default {
  tokens: ['./tokens.json'],
  plugins: [
    pluginSass({
      filename: 'test.scss',
      pluginCSS: {
        filename: 'test.css',
        modeSelectors: [
          {mode: 'l', selectors: ['[data-viewport="l"]']},
          {mode: 'm', selectors: ['[data-viewport="m"]']},
          {mode: 's', selectors: ['[data-viewport="s"]']},
          ],
      },
    }),
  ],
};

...and the tokens.json to reproduce this:

{
    "bodyTextSmall": {
      "$type": "typography",
      "$value": {
        "fontFamily": ["Helvetica", "-system-ui", "sans-serif"],
        "fontSize": "1rem"
      }
    },
    "bodyTextMedium": {
        "$type": "typography",
        "$value": {
          "fontFamily": ["Helvetica", "-system-ui", "sans-serif"],
          "fontSize": "1.5rem"
        }
      },
      "bodyTextLarge": {
        "$type": "typography",
        "$value": {
          "fontFamily": ["Helvetica", "-system-ui", "sans-serif"],
          "fontSize": "2rem"
        }
      },
    "fontalias": {
        "$type": "typography",
        "$value": "{bodyTextLarge}",
        "$extensions": {
            "mode": {
                "l": "{bodyTextLarge}",
                "m": "{bodyTextMedium}",
                "s": "{bodyTextSmall}"
            }
        }    
    }
}

Hope this isn't the way it is supposed to be. :) Thank you!

when building css variables, how to add prefix to to every css variable

  • the css variables names are easy to get conflicts in the examples.

  • instead of current example

:root {
  --color-black: #1b1f24;
  --color-blue0: #ddf4ff;
  --color-blue1: #b6e3ff;
}
  • I want below
:root {
  --ibm-color-black: #1b1f24;
  --ibm-color-blue0: #ddf4ff;
  --ibm-color-blue1: #b6e3ff;
}

Option to exclude meta object from build output

Maybe I'm missing it, but from what I can tell the meta export in the output JS isn't used by anything else in the output. It does however, take up a huge amount of space. Our current build output's JS file is over 11,000 lines and the meta object is responsible for over 10,000 of them. This causes two issues:

  • Unless you have really sophisticated (this behavior might be more common than I originally thought) tree-shaking happening at the sub-file level in your bundler, that makes token code dramatically more expensive to send to a browser.
  • The updated dates in the metadata clutter the output when trying to review updates to the build output in Git (yes I know many workflows don't have include this, but ours does).

Since we aren't using the metadata, its a net negative for us. Having the option to exclude it from the build output would be really nice.

✨ Cobalt 2.0

Planning for Cobalt 2.0 is underway!

Many wonderful people have put Cobalt through the ringer. While I’ve gotten lots of confirmation Cobalt is on the right track working with DTCG tokens, there’s still more needed from it. So to address that, I’ve started planning for the next major update that will meet users’ growing demands, but will require some breaking changes to do so. As of right now, the high points include:

Major changes

  • Plugin chaining. Right now plugins are completely isolated from one another (except for CSS + Sass, which still have lots of rough edges, and whose current interop isn’t a pattern that should be repeated). This has been workable with official plugins, but imposes restrictions on how custom plugins are made.

    I’m currently working on a draft for the Plugin 2.0 syntax, which is basically “the same, but more.” It will give plugins (many) more hooks than just the one build() hook so they can work off one another, and you can transform tokens in multiple passes before preparing them for an output format. And while it’s still a little early for feedback, I’ll open up RFCs in the coming weeks when I’ve proven the changes will simplify current plugins and allow for more complex chaining and better sharing of logic between plugins. The API will still be primarily Rollup/Vite plugin-inspired, but will introduce new concepts where it makes sense (since tokens are fundamentally different than web bundlers). Ideally, this means if you have a custom plugin for 1.x, migrating to 2.x won’t be too bad. But internally, the execution of plugins will differ quite a bit and unlock some pretty cool potential.

  • Support for DTCG 1.0. DTCG 1.0 (stable) is chugging along! The community group is going through the process now of finalizing decisions and fixing some long-requested issues with the working draft spec. TBD if this will be officially stamped and sealed by end of 2024, but I can say that progress is happening.

    • Status: ⌛ Slowly but surely
  • AST-aware errors. Currently, Cobalt throws pretty vague errors when reading tokens.json because it doesn’t keep track of the line it’s on. Likewise for YAML and JSON parsing errors. 2.0 will run error-checking earlier in the parse phase, where it can helpfully print out the correct line in case of a syntax (or schema) error.

    • Status: ✅ JSON (using Momoa) / ✅ YAML

Minor changes

  • Building moved to core. Currently the @cobalt-ui/core package isn’t very useful; it can only validate & normalize the schema but can’t do any meaningful work with the plugins. It wasn’t an intentional design decision to keep most of the building within the CLI package; it was just expedient to do so originally. Moving it to core unblocks people who want to run post-build actions in Node.js without having to read/write from disk (which means it could even be used in APIs, and in the browser).

    • Status: ✅
  • Parsed Tokens object instead of an array. If you’ve built your own plugin in 1.0, you’d know that tokens.json is passed in as a flat array. This will be changed to be a shallow object (with . in keys).

    The original assumption was “since plugins will always be iterating over all tokens anyway, let’s make it simpler,” but that has proven false over time. There are many times when plugins need to grab individual tokens, and tokens.find(…) is not only verbose but slow (comparatively). Objects are closer to the original tokens.json, which makes things simpler to work with. The parsed tokens object will still be shallow, because it is still more time-consuming/more complex to do deep AST traversal than running over a loop once.

    • Status: ✅
  • Getting rid of internal weirdness. There are artifacts internally like metadata which don’t have clear purpose other than “IDK here’s some junk we may need later maybe” (spoiler alert: we didn’t).

    • Status: ✅

Staying the same (no changes)

  • Performance is (still) a priority. I am a huge fan of wasm and languages like Rust, but the nature of working with design tokens is still well within the realm of what Node.js does well all on its own. Adding additional languages on top of Node.js comes with drawbacks including increased difficulty of contributions, more complex debugging, fewer releases, and more difficult plugin authoring. I’m always conscious of how fast (or slow) Cobalt is, but as of now it’s very fast and lightweight, wasm wouldn’t solve any problems, and 2.0 won’t change that.

  • Ease of creating plugins is (still) a priority. Above all, it should be great DX to use Cobalt with existing plugins, and shouldn’t be much harder to write your own. Rollup/Vite continue to be sources of inspiration of timeless APIs that are the perfect blend of simplicity where needed, and complexity where required.

  • No new token types (for now). “Boo!”—I know. This is actually good news, in a sense. If DTCG 1.0 releases this year, it will be a big deal! Figma, Adobe, PenPot, and more have eyes on the DTCG spec, and plan to implement it in their products as soon as possible. Cobalt exists first and foremost to implement that spec as-designed, which maximizes the number of ways you can use Cobalt and its plugins. It will be better for everyone to not have yet another competing standard that forks the spec just to add additional token types.

  • (Still) independent of Style Dictionary. Since Cobalt’s release, Style Dictionary has announced an exciting new v4 with support for DTCG, which I’m looking forward to! Style Dictionary was foundational for so much advancement of design tokens (even DTCG) and I’m excited to see that project continue.

    As a project, Cobalt isn’t seeking to be a 1:1 replacement for SD. Cobalt seeks to provide tooling for the DTCG, and also explore working with design tokens with a plugin-centric API rather than a looser, config-heavy “Gulp-like” one. Ultimately it’s about giving developers more options in tooling to meet different needs, and I love when Style Dictionary works and works well for people. So all that said, Cobalt as a project will continue to explore an API that allows for easy, zero-config usage, but without restricting powerful customizations. Independently of what Style Dictionary is doing. And I hope that both Cobalt and Style Dictionary continue to both grow and develop, and continue to meet developers’ differing needs well!

Note: this is a living document; expect edits (and even some decision reversals) as plans develop and mature.

Release date: Late 2024, with RFCs coming Mar/Apr

To recap, this is mostly a placeholder announcement to check back in at a future date for RFCs and testable beta versions of Cobalt 2.0. While a stable version won’t release before DTCG 1.0 is closer, look out for both RFCs and testable beta versions very soon (in the coming weeks).

So all that to say, is there anything you’d like to see in Cobalt 2.0? At this early stage, any and all ideas are welcome (keeping in mind a Plugin 2.0 API will get an RFC soon)! Is there more Cobalt could do out-of-the-box? Any work that could be moved out of your plugin? Any additional integrations you’d like to see? Leave a comment!

Thanks for reading 🙏

Ability to run plugins on files produced by previous plugins?

Hi Drew,

I've been wanting to write a Prettier plugin for Cobalt UI to familiarise myself a bit more with the tool. The rationale behind plugins for Prettier/ESLint is simple: having it pre-written makes it faster for folks to connect their formatter/linter to their tool and it avoids reliance on things like post NPM scripts, so it runs the same when Cobalt is called via Node directly in CI, for instance.

But I'm under the impression plugins can only get the dictionary as an input and produce files as an output.

I know I've recently told you it'd be great to let plugins modify the dictionary or return a new dictionary instead of files, and I now find myself wanting to do the opposite: having plugins consume files as input!

And I'm also under the impression that Cobalt aims to have individual plugins produce individual outputs, rather than chainable plugins that are combined to produce an outcome (like SD does transform -> format -> action).

Am I missing something? And if not, what's your general stance on making plugins chainable and are there previous discussions/ADRs I could read to understand why Cobalt is designed the way it is currently?

Thanks

Proposal: utility CSS generated by plugin-css

Proposal

I’ve been thinking about #65 and the idea to have a Tailwind plugin, or at least a Tailwind-compatible plugin. While I think that’s probably not in the cards (I believe Tailwind is its own design system, and though you can configure it to a degree, it ultimately does impose assumptions on your DS that restrict it), I do think there’s some middle ground where we could generate Tailwind-like CSS based on your tokens.

For example, given the following tokens:

space:
  $type: dimension
  s:
    $value: 0.25rem
  m:
    $value: 0.5rem
  l:
    $value: 1rem

You could produce the following CSS:

.mt-0 {
  margin-top: 0;
}
.mt-s {
  margin-top: var(--space-s);
}
.mt-m {
  margin-top: var(--space-m);
}
.mt-l {
  margin-top: var(--space-l);
}
/* (and so on …) */

Configuration

But the question is… how? Well, one approach could be a semi-opinionated approach that borrows words from Tailwind, and applies them in the config:

import pluginCSS from '@cobalt-ui/plugin-css';

export default {
  plugins: [
    pluginCSS({
      utilityCSS: {
        color: {
          blue: 'color.core.blue',
          link: 'color.semantic.link',
          error: 'color.semantic.error',
        },
        spacing: {
          s: 'space.s',
          m: 'space.m',
          l: 'space.l',
        },
        // …
      }
    }),
  ],
}

In other words, the top-level keys color and spacing are reserved (color would yield .bg-* and .text-* utility classes, and spacing would yield .m-* and .p-* utility classes).

The mapping here is a bit theoretical, but if you provided a token, it would generate a direct class, e.g. .text-error. But if you provided a group of colors, it would produce multiple classes joined by hyphens, e.g. .text-blue-100.

Alternative config

We could take a more automatic approach, and just have you specify globs of token groups like so:

import pluginCSS from '@cobalt-ui/plugin-css';

export default {
  plugins: [
    pluginCSS({
      utilityCSS: {
        color: ['color.core.*', 'color.semantic.*'],
        spacing: ['space.*'],
      }
    }),
  ],
}

While this may not allow as much fine-tuned control over generated CSS, it would be much easier to set up.

Note that whichever API we go with (even possibly secret option C), I do somewhat like some level of “manual” specification because it would prevent generating a wasteful amount of CSS (Tailwind’s problem, unless you use treeshaking).

Questions

  • Are there other Tailwind-ish words other than color, border, and spacing this should generate?
    • E.g. transition?
  • Is there a better configuration API than either proposal?
    • For example, Tailwind uses numbers for spacing, e.g. .mt-2, however, your design system may not use that convention. What would be a good way to describe that mapping?
  • Is utilityCSS the right top-level term for this?

Allow changing filename generated by plugin-js

Both plugin-css and plugin-sass support passing a filename option to change the name of the generated file.

export interface Options {
  /** output file (default: "./tokens/tokens.css") */
  filename?: string;
  ...

Would it be possible to add the same option to plugin-js?

Error when trying to run "npx co build"

I've gone through all of the instructions and am doing exactly what the original build suggests, however i'm getting this error:

✘ Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol 'c:'  ELIFECYCLE  Command failed with exit code 1.

Can't figure out how to resolve it.

PenPot integration

PenPot is an open-source Figma alternative. Which that alone is worth integrating, but the real win is they support DTCG format out-of-the-box ✨. There wouldn’t even be much work involved; mostly just making sure Cobalt supports PenPot’s JSON output directly.

[question] Token referencing an alias gets raw value?

Setup:
palette.json -> collection of colors, some containing a custom extension that adjusts the raw value
colors.json -> collection of color tokens, each referencing a palette color

For example's sake, let's say my custom extension sets the opacity of an rgba color value.

// palette.json

{
  "palette": {
    "$type": "color",
    "black": {
      "$value": "rgba(0,0,0,100)",
      "$extensions": { "set-opacity": { "opacity": "0.25" } }
    }
  }
}

// colors.json

{
  "colors": {
    "$type": "color",
    "primary": {
      "$value": "{palette.black}"
    }
  }
}

When a plugin is run, such as @cobalt-ui/plugin-js, the value of token in the transform function references the value of the alias before the extensions are run, instead of the computed value. For example:

// tokens.config.mjs

import pluginCustom from './pluginCustom.mjs'

export default {
  tokens: [
    "./src/palette.json",
    "./src/color.json"
  ],
  plugins: [
    pluginJS({
      transform: (token) => {
        console.log(token.$value); // 'rgba(0,0,0,100)'

        console.log(token._original); // { '$value': '{palette.black}' }

        return token.$value;
      },
    })
}

Is this intended? If so, is there a way to either:

  1. Provide an option to have the plugins transform computed tokens (post-extension)
  2. Expose all properties of __original to include extensions

RFC Token Linting

RFC: Token Linting

This week Cobalt added its first linter, a contrast checker (#218) 🎉! This is an exciting development for this project and design ops in general.

Concept

The idea came from a problem I’ve faced as a frontend/design engineer for over a decade: accessibility testing. Designers and devs have manual a11y tools (e.g. Polychrom for Figma and Firefox’s built-in tools), but manual tools are only for designing, and can’t catch regressions. Further, Figma prototypes usually aren’t the “source of truth” that matters anyway.

Automated a11y tooling is more reliable (e.g. Playwright) but is slow (sometimes several minutes per-test) and expensive. And they are usually run on prod, so not only is the user disrupted; the fix has to be re-slotted in amidst planned work and disrupts the flow (i.e. it’s “an outage”). On top of all that, the tests themselves are time-intensive to set up, and may not catch all combinations.

However, linting can be every bit as accurate and effective as the previous two methods, but is cheapest and fastest, and sitting in CI it doesn’t disrupt the development cycle. It only hasn’t been a thing before because teams haven’t had their design systems codified in statically-analyzable DTCG tokens.json, which contains all the data on which colors can/can’t be used together. And with newer a11y testing methods like APCA that take typography into account, we have all the info right there in tokens.json.

Taking a step back, it’s also worth asking “is static analysis (linting) the right tool for the job here?” And at least for contrast, the answer is yes. All the current respected a11y tests are all just static analysis; the only reason they get run in production is because we had a missing piece before, which was all the design tokens collected in a JSON file.

None of what I’m describing is new, but the exciting development is pairing linting + design tokens. This is a relatively new area I think Cobalt seems poised to solve.

RFC

Down to business: what does this look like? See the lint-a11y plugin as the proof-of-concept:

// tokens.config.js
import a11y from "@cobalt-ui/lint-a11y";

/** @type {import("@cobalt-ui/core").Config} */
export default {
  tokens: "./tokens.json",
  outDir: "./tokens/",
  plugins: [a11y()],
  lint: {
    rules: {
      'foo/bar': 'error',
      'bar/baz': 'warn',
      'baz/bat': 'off',
    },
  },
};

The API is modeled heavily off of ESLint (even internally), and works much the same way. The basic workflow is: a) plugins register rules (with defaults), b) users configure (or disable) rules, and b) those lint checks run on every build.

Currently the checks run on co build and co lint, but can be disabled with lint.build.enabled: false.

So open questions are:

  1. Does the ESLint model work for design token linting, or are there other APIs that work better?
  2. (Keeping in mind 2.0 will support AST improvements) What’s missing from this being a proper linter?
  3. How are all the ways Design Tokens could be linted?

CSS Plugin: Convert alias to raw values?

Is there a way to convert aliases into raw values? Didn't find this in docs.

convert this:

 --clr-theme-container-light: var(--clr-core-ntrl-100);

to this:

 --clr-theme-container-light: #fffff;

Revisit Figma import/export using Variables

Proposal

Cobalt CLI should be able to natively export Figma Variables → tokens.json, and sync tokens.json → Figma Variables (with auth, of course).

Context

Cobalt CLI needs an update to its Figma import/export functionality. It’s worth noting that Cobalt predates Figma variables, so understandably the original import/export using styles & components was a bit unwieldy—there’s not only a disconnect between Figma’s concepts of styles, but there’s also an expectation to support design token type that live outside the W3C spec. That made the integration difficult to maintain, and frustrating to use.

But along come Figma Variables, which despite not being called “tokens” are directly compatible with the W3C Design Token spec. Figma even has official example code showing how to accomplish going straight from Figma → W3C DTCG format and vice-versa, with no third parties.

Cobalt can use Figma’s example as a starting base, and make the flow smooth. The end result should be fairly unopinionated, and much smoother.

Tokens Studio for Figma

As a stopgap for removing direct tokens syncing, Cobalt added support for Tokens Studio for Figma in 1.1.0. The free version of this service is a better transition layer between Figma and Design Tokens than what Cobalt ever previously supported with styles. This integration will remain; the proposal is simply adding a way to import/export Figma Variables directly for people who don’t use Tokens Studio.

Figma Styles

Cobalt still won’t be able to import/export styles. Those are different enough, and have enough divergence to be out-of-scope for this project. However, if people choose to opt-in to syncing Variables from Cobalt to Figma, it’s not too hard for designers to copy Variable updates to styles manually.

Nested plugins

Hi. First, thank you for authoring Cobalt. Coming from Style Dictionary, I appreciate the attention to the upcoming design tokens format specification.

The following is more of a question, not a bug.

We like to use Storybook to showcase our design tokens, including examples. My initial thought was to use the JS plugin to export a JSON file. What I'm noticing, is that composite tokens like shadow keep their original shape, and are not transformed into a computed value (e.g. CSS).

"shadow.active": [
  {
    "offsetX": "0",
    "offsetY": "2px",
    "blur": "2px",
    "spread": "0",
    "color": "rgb(0 0 0 / 24%)",
    "inset": false
  }
],

vs

"shadow.active": [ "0 2px 2px 0 rgb(0 0 0 / 24%)"],

It totally makes sense why the JSON format would not include transformed values.

Therefore, I set out to write a custom plugin to generate a JSON file that provided values transformed for CSS. Ideally, I wanted to somehow leverage the official CSS plugin since it already contains all that juicy logic to transform tokens into CSS.

The challenge is that a plugin bundles the transformation and formatting logic in a single step. Neither piece can be imported and used separately. To leverage an existing plugin, you let it do its thing and return the formatted output, then you use regex to adjust the output into something else. For example:

import pluginCSS from '@cobalt-ui/plugin-css'

export default function pluginObjectStyles(options = {}) {
  return {
    name: 'plugin-css-object-styles',
    async build({ tokens, metadata }) {
      const raw = await pluginCSS(options).build({ tokens, metadata })
      const { filename, contents: css } = raw[0]

      const cssAsJs = /* ... a bunch of regex to convert contents to an exported JS object */
      /*
      E.g.
      :root {
        --color-red: red;
      }
      to...
      export const tokens = {
        "--color-red": "red"
      }
      */

      return [
        {
          filename,
          contents: cssAsJs,
        },
      ]
    },
  }
}

Is that the recommended approach to reusing plugins? Should transformation and formatting be separate steps that could be imported and utilized separately? Curious how you would approach this? Thanks.

Default group values

There's a lot of (stale) discussion in design-tokens/community-group#97 (in which you contributed early on!), but it seems like the proposal from c1rrus is a pretty clear path forward and avoids name conflicts.

I know you don't want to stray too far from the spec, especially if things could change, but I wanted to see how open you would be to adding a $default key for allowing tokens defined at the group level?

This input would translate to js as:

{
  "accent": {
    "$default": {
      "$value": "{colors.purple.700}"
    },
    "weak": {
      "$value": "{colors.purple.300}"
    }
  }
}
export const tokens = {
  'accent': '#5721CC',
  'accent.weak': '#BA9CFF',
};

typescript: token function typings

Hello, it's me again 😆

your latest fix to #17 doesn't work as expected.

I doesn't complains about mode as second argument needed anymore. Great.

But I don't get intellisense for mode (tested with your apple example):
SCR-20221122-o6x

Also, I can put anything in both slots:
SCR-20221122-o79

Plugin Request: Swift / iOS

Plugin Request: Swift / iOS

For any Swift/iOS developers, I’d love to assist in making a Cobalt plugin. Doesn’t have to be an official Cobalt plugin. Can be a part of this repo, or if you’ve made your own I’d love to add it to the documentation! Just want to unblock Swift developers from using the DTCG format, but as I am not a Swift developer, and would probably make all the wrong choices, I am not sure where to even begin adding this.

If someone could get a plugin started, I’d be happy to own maintenance if required. Or if it’s something someone would like to maintain themselves, happy to support them and link in documentation to their project. Either works!

bug: wrong typing for param of token function

Hey,

As the title says, typings for token function (the overload for modes) is wrong. This results in Typescript doesn't error when passing non-valid token names.

SCR-20221028-w5h

SCR-20221028-vy0

After changing to tokens it screams like it should :)

SCR-20221028-w65

Great library btw! Very promising.

Splitting tokens into separate files

As the number of tokens increase, it would be helpful to be able to split those up into separate files for maintainability. A rough proposal might look like this:

// tokens.json
{
  "colors": {
    "$include": "./colors.json"
  }
}
// colors.json
{
  "$type": "color",
  "white": "#FFFFFF",
  "purple": {
    "light": {
      "$value": "#F1EAFF"
    },
    "dark": {
      "$value": "#331673"
    }
  }
}

Which for parsing would compile that into

{
  "colors": {
    "$type": "color",
    "white": "#FFFFFF",
    "purple": {
      "light": {
        "$value": "#F1EAFF"
      },
      "dark": {
        "$value": "#331673"
      }
    }
  }
}

Alternatives could be

// a
// probably the simplest in code to directly replace a value, but is ambiguous and maybe hard to detect accurately
{
  "colors": "./colors.json"
}

// b
// doesn't feel great since you're conflating the top-level types for colors
{
  "colors": {
    "$type": "include",
    "$value": "./colors.json"
  }
}

Happy to hear your thoughts!

Supernova integration

With the release of Supernova 2.0 that supports all of the DTCG spec, Cobalt should test compatibility with their export-to-GitHub workflow.

While I’m not interested in supporting every commercial/private product, this is the first product I’m aware of that has first-class support for exporting DTCG, so theoretically it should export files the same as the many tokens.json examples in this project. But I’m also interested in testing some of the metadata Supernova generates, and just use it as a learning experiment overall.

Typography token aliases broken in mode-output of plugin-css

The same bug I reported a few days ago (thank you very much for the quick fix btw!) managed to hide away in another spot:

I'm afraid it is still happening within mode-output:

[data-viewport="xl"] {
  --fontalias-0: v;
  --fontalias-1: a;
  --fontalias-2: r;
  --fontalias-3: (;
  --fontalias-4: -;
  --fontalias-5: -;
  --fontalias-6: b;
  --fontalias-7: o;
  --fontalias-8: d;
  --fontalias-9: y;
  --fontalias-10: T;
  --fontalias-11: e;
  --fontalias-12: x;
  --fontalias-13: t;
  --fontalias-14: );
}

Here's the code to reproduce it:

tokens.json:

{
    "bodyText": {
      "$type": "typography",
      "$value": {
        "fontFamily": ["Helvetica", "-system-ui", "sans-serif"],
        "fontSize": "1.5rem",
        "fontStyle": "normal",
        "fontWeight": 400,
        "letterSpacing": 0,
        "lineHeight": 1.5,
        "textTransform": "none"
      }
    },
    "fontalias": {
        "$type": "typography",
        "$value": "{bodyText}",
        "$extensions": {
            "mode": {
                "xl": "{bodyText}"
            }
        }    
    }
}

tokens.config.mjs:

import pluginCSS from '@cobalt-ui/plugin-css';
/** @type {import('@cobalt-ui/core').Config} */

export default {
  tokens: ['./tokens.json'],
  plugins: [
    pluginCSS({
      modeSelectors: [
        {mode: 'xl', selectors: ['[data-viewport="xl"]']}
      ]
    })
  ]
};

Cobalt is a game-changer for our design system btw. Thank you!

Help wanted: `@cobalt-ui/plugin-ts`

The TypeScript plugin currently isn’t much different from the JSON plugin; it essentially just creates JSON assigned to exportable variables, sprinkles in a few types, etc.

It would be great if the TypeScript plugin could be improved with the following:

  • Proper formatting (Prettier?)
  • Improved utility functions for modes and the like

Support for boolean and string types?

Hey there,

I'd like to start a discussion on the topic of boolean and string type tokens and if it would be beneficial for Cobalt to support them.

Why to support a boolean type?

First and foremost: It would bridge the gap between Figma and the dev world. While there may be other creative uses, I think boolean variables in Figma are mainly used to hide or show layers. This also alleviates the need for hacks to story visibility information (like fully transparent color tokens) and therefore stays closer to what is done in development.

If we're talking about only storing visibility information however, one could argue that a token type based on CSS properties might be a more specific way to go. You could have a "visibility" type with values "visible" and "hidden" (which is what Supernova seems to be doing). However having "visibility" on "hidden" does not behave like hidden layers in Figma do - for that you'd need a "display: none".

But since we can't be entirely sure if a boolean coming from Figma would indeed be used for visibility purposes, here are some thoughts on how to handle boolean in Cobalt:

  • JSON: type "boolean" as in the JSON standard type, value true or false. "true" and "false" as strings would throw an error. Common Figma plugins that export JSON also export boolean variables as JSON standard booleans.
  • CSS: Since it's hard to do anything with true or false in CSS, the values 1 and 0 could be used for CSS and custom properties: This would make it easier to do logical operations even in CSS (see here or here).
  • JS/SASS: For JS and SASS, standard boolean true and false should be used. I realize this could be problematic in combination with the 1 and 0 CSS solution, since Cobalt also allows for CSS custom properties to be assigned to SCSS variables...

Why to support a string type?

It would be great if you could use tokens to store default labels/texts/prefixes, placeholder text, current mode names, default error messages and so on. Also, it would again be on par with Figma to enable import and not lose half of the variables / tokens on the way.

In contrast with boolean, this seems to be rather straightforward:

  • JSON: type "string" as in the JSON standard type.
  • String value can also be empty (""), as it could be filled later. For example: prefixes for accessibility.
  • CSS/JS/SASS could all use the string equally.

Especially for boolean, this isn't as straightforward as I initially thought, so I'd love to hear your thoughts. Thanks and all the best!

Possible bug in plugin-css with typography tokens

Hello,

I came across an issue with typography tokens today:

So "d.font.display" is an alias for the base typography token "o.font.10".
Both are $type typography.

For some reason, the output of plugin-css breaks up the reference "var(--o-font-10)" letter by letter.

  --d-font-display-0: v;
  --d-font-display-1: a;
  --d-font-display-2: r;
  --d-font-display-3: (;
  --d-font-display-4: `-;`
  --d-font-display-5: -;
  --d-font-display-6: o;
  --d-font-display-7: -;
  --d-font-display-8: f;
  --d-font-display-9: o;
  --d-font-display-10: n;
  --d-font-display-11: t;
  --d-font-display-12: -;
  --d-font-display-13: 1;
  --d-font-display-14: 0;
  --d-font-display-15: );

The same happens to all my typography tokens.

Is this a known bug? Thank you and keep up the amazing work!
Torsten

Allow empty groups?

I've noticed that Cobalt errors when it encounters an empty group in a DTCG file. In my current setup, which uses Tokens Brücke to export DTCG from our Figma library's variables, I end up with an empty group. (FWIW, the reason we have an empty group is because our design system is still a work in progress, and we're scaffolding out groups before adding actual tokens into them.)

I've worked around this with a simple script that removes the offending group from the DTCG file, before it gets passed into Cobalt. However, I think it would be helpful if Cobalt was more permissive and just ignored empty groups rather than throwing an error. (or else this was downgraded to a warning)

It would simplify my setup a little. But I suspect it's a situation others might also encounter, so it could help others too.

FWIW, the DTCG spec doesn't say anything about empty groups, so Cobalt's approach is a valid interpretation. However, I'll raise an issue in the DTCG repo later to suggest amending the spec to be more specific about whether (or not?) empty groups are allowed.

Figma integration fails with multiple collections and variables modes

The figma integration seems to fail when an alias variable contains multiple modes but references a primitive without the same mode.

Tested with this file 1
https://www.figma.com/community/file/1299749257125559105/remarkable-ui

Error

 Exception has occurred: Error: 📦 Common.Neutral.Lowest#Light: can’t find {color.light.100#Light}

Details

The file contains a color.light.100 but no color.light.100#Light because the primitives collection does not have Light mode

image
image

Figma API extract
[{
  id: "VariableID:214:71",
  name: "📦 Common/Neutral/Lowest",
  remote: false,
  key: "08ce2f5577343ea6f9d68ecba3c0ea63737224b0",
  variableCollectionId: "VariableCollectionId:203:4",
  resolvedType: "COLOR",
  description: "",
  hiddenFromPublishing: false,
  valuesByMode: {
    "203:0": {
      type: "VARIABLE_ALIAS",
      id: "VariableID:26:314",
    },
    "1104:1": {
      type: "VARIABLE_ALIAS",
      id: "VariableID:25:24",
    },
  },
  scopes: [
    "ALL_SCOPES",
  ],
  codeSyntax: {
  },
},
{
  id: "VariableID:26:314",
  name: "color/light/100",
  remote: false,
  key: "a7c4fb23a941ed6a9ee9c98dbbf7bf8025e05495",
  variableCollectionId: "VariableCollectionId:16:9",
  resolvedType: "COLOR",
  description: "",
  hiddenFromPublishing: false,
  valuesByMode: {
    "16:1": {
      r: 1,
      g: 1,
      b: 1,
      a: 1,
    },
  },
  scopes: [
    "ALL_SCOPES",
  ],
  codeSyntax: {
  },
}]

Footnotes

  1. I removed % sign in variable names on my copy to be sure it was not the source of the problem but same issue.

Docs blockquotes accessiblitly concerns

<blockquotes> are being used throughout the docs to provide call out notes. There are a number of them, but here are a couple for the sake of example.

There are two accessibility issues with the current implementation.

Contrast

The white text on the light blue background is really not perceivable. I'm measuring a contrast ratio of 1.16:1, but text that size should be at least 4.5:1. This is a major issue for any sighted user. It looks like the background color is set in styles/_md.scss. I'm not a designer and don't really have a preference as to how this gets solved, but one easy way to do it would be using a darker blue for the background color. blue-50 gets us up to 6.26:1 with the body text, but still leaves links below the threshold at 3.31:1.

Semantics

While not as big of an issue as the first, the second is the misuse of the <blockquote> element for styling purposes. This is unfortunately pretty common in docs sites, but <blockquotes> should only be used if the enclosed text is actually a quotation. Better options here would be to either use the <aside> element or the note role (and probably move the related styling to a class). i realize markdown doesn't have a built in method for either of those approaches like the way a > lets you add a <blockquote>, but it should be possible to write the correct HTML directly inside the markdown file.

no intellisense for scss `token` function

I don't get any intellisense when using the token function in scss.

Bildschirmfoto 2022-10-28 um 22 57 06

Should I get intellisense here? At least the docs (kinda) say so:

If you‘re using Sass, this method is recommended because token() will safely guard against accidental typos for your design tokens

My Setup works (Nuxt 3 app in monorepo) -> putting in a correct name renders as expected in the browser.

Contrast Tests (and others?)

Proposal

Many developers use contrast tests either through annual audits, or E2E tests. And there are tools in Figma that alert of any contrast issues. But what if you could automatically run contrast checks just based on what’s in tokens.json? What if you could, say, specify a foreground color, background color, and typography style, and have Cobalt test you’re meeting the minimum contrast ratios? Cobalt could even take modes into account as well!

The main idea is whenever you run co build it would fail with an error if tests didn’t pass (or could be a separate co test command; not sure). You could make as many token groups as you’d like, and assert whatever levels of compliance you need.

The simple contrast checker that most are familiar with is the simple WCAG 2 model which lets you naïvely compare any 2 colors and get a contrast ratio. But there are bigger, scarier contrast ratios like APCA which not only give different scores based on which color is foreground and which is background, but also your font family, size, and weight used with those colors. Such a complex algorithm would be great to implement close to your tokens, especially when running in CI!

Possible implementations

1. Config

The simplest implementation, e.g.:

export default {
  tests: [
    {
      type: 'contrast',
      tokens: {
        foreground: '{color.semantic.text}',
        background: '{color.semantic.bg}',
        typography: '{typography.base}',
        modes: ['light', 'dark'],
      },
      expect: {
        apca: 'Lc 90',
        wcag2: ‘AAA',
      },
      severity: 'error', // 'error' | 'warn' | null,
    },
  ],
}

Pros: easy to test the idea, easy for users to quickly write tests

Cons: very limiting, as it won’t allow for “custom” user tests; either a test is built-in or not supported (so realistically there’d only be a couple test types)

2. Lint plugins

Plugins get a new lint stage, which means they could accept any possible arguments for that stage (here, checks, but this could be named anything, and plugins could even reuse config you’ve already passed in).

import contrast from '@cobalt-ui/plugin-contrast';

export default {
  plugins: [
    contrast({
      checks: [
        {
          tokens: {
            foreground: '{color.semantic.text}',
            background: '{color.semantic.bg}',
            typography: '{typography.base}',
            modes: ['light', 'dark'],
          },
          expect: {
            apca: 'Lc 90',
            wcag2: ‘AAA',
          },
          severity: 'error', // 'error' | 'warn' | null,
        },
      ],
    }),
  ],
}

Sure, the syntax seems similar to #1, but under-the-hood has lots of differences:

Pros: users can write custom tests, plugins can have additional “lint” awareness, and the tests could use more plugin-aware code (e.g. maybe in addition to your core tokens.yaml lint rules, you have additional linters that kick in when outputting CSS vars, such as warning on P3-gamut colors?)

Cons: if plugins try and be both “lint” and “build” plugins it can be a lot of work on the maintainer

3. Testing

This ticket uses the word “testing” as a catchall, but in this option I mean testing-testing: like actually shipping a true testing package that executes inside a runner:

// contrast-test.tokentest.js

import { assert, test } from 'node:test';
import { passesContrast } from '@cobalt-ui/testing';

const colorPairs = [/* (foreground, background, typography tokens) */];

for (const colorPair of colorPairs) {
  test("color combo passes accessibility", () => {
    assert.deepStrictEqual(passesContrast('./tokens.json', colorPair), { apca: 'Lc 90', wcag2: ‘AAA' });
  });
}

Pros: power, unlimited power!!!

Cons: this is woefully over-engineered, and would not be fun for me to maintain. Unit tests, by their nature, test runtime code. Tokens are statically-analyzable and there is no need to pretend they have runtime. This is not a serious option; I just wanted to write down an idea.


Of the three, I’m much leaning toward #2 as the option with the most potential, and could even be shipped in v1 without waiting for the 2.0 plugin API. Plus, kicking the tires now may inform how this system carries over into 2.x.

Other tests?

Other than contrast, are there any other “tests” that would be helpful to run on your design tokens? Ideally ones that can be run programmatically and not require any heavy browser setup or anything.

We could have additional types like even token ID assertion, or that certain modes exist, as well.

CSS Plugin default transform should expose the same API and params with the custom one

Hello,

Thank you so much for Cobalt its been a life saving module so far :)

I have an issue with custom transforms when using the CSS Plugin. Since this commit there have been some changes on how the internals work that could break or not bring the same flexibility for custom build transforms.

For example the current API for transform transform(token, mode) {} only has access to token and mode but if we want to reproduce the capabilities of the existing default transform we can't unless we reimplement some of the functionality ourselves.

For example looking at the typography one

case 'typography': {
      const {value, originalVal} = getMode(token, mode);
      if (typeof originalVal === 'string') {
        return varRef(originalVal, tokens, generateName);
      }
      const output: Record<string, string> = {};
      for (const [k, v] of Object.entries(value)) {
        const formatter = k === 'fontFamily' ? transformFontFamily : (val: any): string => String(val);
        output[kebabinate(k)] = isAlias((originalVal as any)[k] as any) ? varRef((originalVal as any)[k], tokens, generateName) : formatter(v as any);
      }
      return output;
    }
    default: {
      throw new Error(`No transformer defined for $type: ${(token as any).$type} tokens`);
    }
  }

varRef

This needs new params that are not exposed to the transform() params. For example tokens: ParsedToken[], generateName: ReturnType<typeof makeNameGenerator> are needed and we can't make use of it.

We can get the list of parsed tokens via the core parser but that means we need to parse them again when they are already available.

And that is just one example.

I think the capabilities of the default transformer should be exposed in a reusable way to make sure we can reimplement the needed functionality.

There are more items to talk about this and I am wiling to help do that but just wanted to get your thoughts on it first.

Plugin Request: Android

Plugin Request: Android

Would love to also add a plugin to generate Android code from the DTCG format. Is it Java? Flutter? JSON? Who knows! That is up to an actual Android developer to figure out, and not me. Would love to assist someone in any way, whether that’s getting a plugin started in this repo and I own maintenance of it, or I support you manage your own separate project and I link to it in the docs and encourage people to use your project. Either works!

Mode output in plugin-sass (CSS variable mode) uses the tokens own css-variable for all modes, not their respective targets

Hello again!

We're using plugin-sass together with plugin-css in "CSS variable mode" and found that mode output in the .scss-file does not seem to work as we would expect. As an example, we've defined a token "redgreenblue" with 3 modes (red, green and blue) that point to (duh) a "red", "green" and "blue" token.

However, the .scss outputs it like this and uses the tokens own CSS variable for all its modes, not their respective targets.

  "redgreenblue": (
    default: (var(--redgreenblue)),
    "red": (var(--redgreenblue)),
    "blue": (var(--redgreenblue)),
    "green": (var(--redgreenblue)),
  )

Here's the token-JSON to reproduce this:

{
    "red": {
      "$type": "color",
      "$value": "#ff0000"
      },
    "blue": {
        "$type": "color",
        "$value": "#00ff00"
      },
    "green": {
        "$type": "color",
        "$value": "#0000ff"
      },
    "redgreenblue": {
        "$type": "color",
        "$value": "{red}",
        "$extensions": {
            "mode": {
                "red": "{red}",
                "blue": "{blue}",
                "green": "{green}"
            }
        }    
    }
}

... and the tokens.config.mjs to go along with it. :)

import pluginSass from '@cobalt-ui/plugin-sass';
/** @type {import('@cobalt-ui/core').Config} */

export default {
  tokens: ['./tokens.json'],
  plugins: [
    pluginSass({
      filename: 'test.scss',
      pluginCSS: {
        filename: 'test.css',
        p3: false
      },
    }),
  ],
};

Thanks again and have a very nice day!

Enable granular CSS variables output

Please enable configuring what tokens to exclude from the output. This would simply make generating CSS variables a lot more flexible and allow optimizing for specific use cases where a token or a token group is not needed. Below is a suggestion how this might work in practice. Adding ignorePatterns into the config would allow setting what tokens or groups to exclude from output.

/** @type {import('@cobalt-ui/core').Config} */
export default {
  tokens: './tokens.yaml',
  ignorePatterns: ['typography.font.*']
};

Currently the only option is to use modes which are intended for a different use case and would result in a lot of unconvenience.

Modes can't begin with numbers

I created a "fontSize" dimension with a variety of modes, but I was surprised to find out that mode names can't be prefixed with numbers

Screenshot 2023-08-28 at 5 15 08 PM

Is this expected? Or is this a bug?

Make token private

How do i label some tokens as being private? I have some tokens that i want to exist soley to be referenced by aliases, but not be included with the final output?

CSS modes documentation is incorrect

The documentation for modeSelectors in the css plugin has multiple examples, some of which are failing. On the css plugin docs, modes do not include the # selector in the key, but on the modes guide they do. Looking at this commit from Jan 2022, the # is definitely required, so right now the css plugin docs are incorrect.

However, I'd really like to see the # requirement dropped, since we have modes defined at the semantic level rather than the color level (e.g. background-strong.light, background-strong.dark which alias to static colors). I'm not sure if you were planning on dropping the # requirement, or if there's a typo in the plugin docs.

Nested object support in the js plugin

For some cases, having a proper nested object for js tokens would be preferrable over dot-notation strings:

// fine, but not optimal sometimes
{
  'colors.gray': '#123456',
  'colors.black': '#000000',
  'colors.white': '#FFFFFF',
}

// would be better in some instances
{
  colors: {
    gray: '#123456',
    black: '#000000',
    white: '#FFFFFF',
  }
}

Would you be open to a config option to output a nested object rather than dot-notation string accessors?

CSS Custom Props Have Spaces

It's possible for token or group object keys to contain a space. To the best of my knowledge, this is not disallowed by the spec (though specific cases like the empty string and leading/trailing white space are being considered).

Those space don't currently cause any issues (that I can tell) with Cobalt parsing the tokens, and the rendered output by the JS plugin is valid, using strings as keys. But they become a problem when trying to use the CSS plugin to produce custom properties. CSS custom properties cannot have spaces in them, but Cobalt renders the spaces as is anyway creating invalid CSS.

Example

Tested with

  • "@cobalt-ui/cli": "^1.6.0",
  • "@cobalt-ui/plugin-css": "^1.5.0",
  • "@cobalt-ui/plugin-js": "^1.4.1"

Given the following tokens input

{
  "Full Color Palette": {
    "Magenta": {
      "Magenta-50": {
        "$type": "color",
        "$value": "#fff0f6",
        "$description": ""
      },
      "Magenta-100": {
        "$type": "color",
        "$value": "#ffd6e7",
        "$description": ""
      }
    }
  }
}

Running npx co build produces the following output:

A valid `index.js` file
/**
 * Design Tokens
 * Autogenerated from tokens.json.
 * DO NOT EDIT!
 */

export const tokens = {
  'Full Color Palette.Magenta.Magenta-50': '#fff0f6',
  'Full Color Palette.Magenta.Magenta-100': '#ffd6e7',
};

export const meta = {
  'Full Color Palette.Magenta.Magenta-50': {
    _original: {
      $type: 'color',
      $value: '#fff0f6',
      $description: '',
    },
    _group: {
      id: 'Full Color Palette.Magenta',
      $extensions: {
        requiredModes: [],
      },
    },
    id: 'Full Color Palette.Magenta.Magenta-50',
    $type: 'color',
    $value: '#fff0f6',
    $description: '',
  },
  'Full Color Palette.Magenta.Magenta-100': {
    _original: {
      $type: 'color',
      $value: '#ffd6e7',
      $description: '',
    },
    _group: {
      id: 'Full Color Palette.Magenta',
      $extensions: {
        requiredModes: [],
      },
    },
    id: 'Full Color Palette.Magenta.Magenta-100',
    $type: 'color',
    $value: '#ffd6e7',
    $description: '',
  },
};

export const modes = {};

/** Get individual token */
export function token(tokenID, modeName) {
  if (modeName && modes[tokenID] && modeName in modes[tokenID]) return modes[tokenID][modeName];
  return tokens[tokenID];
}
An invalid tokens.css file
/**
 * Design Tokens
 * Autogenerated from tokens.json.
 * DO NOT EDIT!
 */

:root {
  --Full Color Palette-Magenta-Magenta-50: #fff0f6;
  --Full Color Palette-Magenta-Magenta-100: #ffd6e7;
}

@supports (color: color(display-p3 1 1 1)) {
  :root {
    --Full Color Palette-Magenta-Magenta-50: color(display-p3 1 0.9411764705882353 0.9647058823529412);
    --Full Color Palette-Magenta-Magenta-100: color(display-p3 1 0.8392156862745098 0.9058823529411765);
  }
}

Options

  1. This a known and expected limitation of the CSS plugin
    • If so, it would be a good idea to document it since this isn't an issue with other formats.
  2. The plugin replaces any spaces in a key with a - the same way it does in between group and token names
  3. The plugin replaces any spaces in a key with an _ to distinguish between spaces within a name and the split between different token and group names
  4. The developer is given the ability to handle this in the plugin's config. Maybe an expansion of the transform option to allow editing more than the value, or another option all together.

I think option 3 is my personal preference, but I don't have a strong opinion. Just hoping this can get resolved quickly.

Thanks for your work on this tool! Looking forward to seeing the design tokens space mature.

Allow arrows of shadows

@drwpow i definitely agree with your recommendation you made here to support multiple shadows, my specific use case is leveraging designs from Material UI, whose shadow tokens are an array of 3 shadows

"0px 2px 1px -1px rgba(0,0,0,0.2),
0px 1px 1px 0px rgba(0,0,0,0.14),
0px 1px 3px 0px rgba(0,0,0,0.12)"

It sounds like you were considering expanding your tool to allow arrays, is that still the case?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.