Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add best practices for token types #182

Merged
merged 5 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 57 additions & 39 deletions docs/advanced/plugin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Creating your own Cobalt plugins is easy if you’re comfortable with JavaScript

## Plugin Format

A Cobalt plugin is designed similarly to a Rollup or Vite plugin, if you’re familiar with those (no worries if you’re not). A plugin is essentially **any function that returns an object with the following keys**:
A Cobalt plugin is designed similarly to a [Rollup](https://rollupjs.org/plugin-development/) or [Vite plugin](https://vitejs.dev/guide/api-plugin), if you’re familiar with those (no worries if you’re not). A plugin is essentially **any function that returns an object with the following keys**:

| Key | Type | Description |
| :------- | :--------: | :----------------------------------------------------------- |
Expand Down Expand Up @@ -89,11 +89,51 @@ export default {

You can then expand `options` to be whatever shape you need it to be.

## `name`
### `name`

Naming your plugin helps identify it in case something goes wrong during the build. You can name your plugin anything.

## `config()`
### Token structure

Cobalt gives you more context when dealing with tokens. Inspecting each individual token will yield the following:

```js
{
id: 'color.brand.green', // the full ID of the token
$type: 'color', // the original $type
$value: '#40c362', // the normalized $value
$extensions: {
mode: {…} // normalized modes
},
_group: {…} // metadata about the token’s parent group
_original: {…} // the original node untouched from tokens.json (including unresolved aliases, etc.)
}
```

### Tips

- For `build()`, return a **relative filename**. That way it respects the user’s `outDir` setting.
- Returning only one file is normal! Most plugins only output one file.
- Use JSDoc comments as much as possible! They go a long way in good DX of your plugin.
- For the full ID of the token, a dot (`.`) always represents a group. So for `color.brand.green`, you’re looking at the `green` token, inside the `brand` group, inside the `color` group. Groups aren’t allowed to have dots in their names.
- Cobalt will always resolve `$value` to the final value, even for aliased tokens. To see the original alias name, see `_original.$value`.

## Lifecycle

Cobalt executes in the following order:

1. **Plugin instantiation.** All plugins are loaded, in array order, in the user’s config.
2. **Config.** `config()` is called on every plugin (if present), also in array order. Note that if any plugin modifies the config, the changes will only be picked up by plugins that appear later in the array.
3. **Build.** `build()` is called on every plugin, in parallel.
4. **Write.** Cobalt writes each plugin’s file(s) to disk after it’s done. This also happens in parallel, and happens as soon as each plugin finishes.

::: info

In an upcoming release (TBD), Cobalt will add some build hooks soon from the [Rollup Plugin API](https://rollupjs.org/plugin-development/#build-hooks), including, but not limited to, [load](https://rollupjs.org/plugin-development/#load), [buildStart](https://rollupjs.org/plugin-development/#buildstart), and [buildEnd](https://rollupjs.org/plugin-development/#buildend).

:::

### `config()`

The `config()` function is an optional callback that can read the final user config or modify it. Use it if you need to read a user’s setting. Though you _can_ mutate the config, don’t do so unless absolutely necessary!

Expand Down Expand Up @@ -121,9 +161,9 @@ export default function myPlugin(): Plugin {

`config()` will be fired _after_ the user’s config has been fully loaded and all plugins are instantiated, and _before_ any build happens.

## `build()`
### `build()`

The `build()` function takes one parameter object with 3 keys:
The `build()` function is the equivalent of Rollup’s [transform](https://rollupjs.org/plugin-development/#transform) hook. It takes one parameter object with 3 keys:

| Name | Type | Description |
| :---------- | :-------------------: | :---------------------------------------------------------- |
Expand Down Expand Up @@ -154,40 +194,6 @@ export default function myPlugin(): Plugin {
}
```

### Token structure

Cobalt gives you more context when dealing with tokens. Inspecting each individual token will yield the following:

```js
{
id: 'color.brand.green', // the full ID of the token
$type: 'color', // the original $type
$value: '#40c362', // the normalized $value
$extensions: {
mode: {…} // normalized modes
},
_group: {…} // metadata about the token’s parent group
_original: {…} // the original node untouched from tokens.json (including unresolved aliases, etc.)
}
```

### Tips

- For `build()`, return a **relative filename**. That way it respects the user’s `outDir` setting.
- Returning only one file is normal! Most plugins only output one file.
- Use JSDoc comments as much as possible! They go a long way in good DX of your plugin.
- For the full ID of the token, a dot (`.`) always represents a group. So for `color.brand.green`, you’re looking at the `green` token, inside the `brand` group, inside the `color` group. Groups aren’t allowed to have dots in their names.
- Cobalt will always resolve `$value` to the final value, even for aliased tokens. To see the original alias name, see `_original.$value`.

## Lifecycle

Cobalt executes in the following order:

1. **Plugin instantiation.** All plugins are loaded, in array order, in the user’s config.
2. **Config.** `config()` is called on every plugin (if present), also in array order. Note that if any plugin modifies the config, the changes will only be picked up by plugins that appear later in the array.
3. **Build.** `build()` is called on every plugin, in parallel.
4. **Write.** Cobalt writes each plugin’s file(s) to disk after it’s done. This also happens in parallel, and happens as soon as each plugin finishes.

## Testing

To test your plugin working on your design tokens, add it to your `tokens.config.mjs`:
Expand All @@ -203,6 +209,18 @@ export default {

Now when you run `co build`, your plugin will run and you can see its output.

## Working with Token Types

See the **Tips & recommendations** section of token pages to learn more about working with each time:

- [Color](/tokens/color#tips-recommendations)
- [Dimension](/tokens/dimension#tips-recommendations)
- [Font Family](/tokens/font-family#tips-recommendations)
- [Duration](/tokens/duration#tips-recommendations)
- [Cubic Bézier](/tokens/cubic-bezier#tips-recommendations)
- [Gradient](/tokens/gradient#tips-recommendations)
- [Shadow](/tokens/shadow#tips-recommendations)

## Examples

Examples of plugins may be found [in the original source repo](https://github.com/drwpow/cobalt-ui/tree/main/packages).
10 changes: 5 additions & 5 deletions docs/package.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
{
"name": "@cobalt-ui/docs",
"name": "cobalt-ui-docs",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vitepress dev",
"build": "del .vitepress/dist && vitepress build && cp _redirects .vitepress/dist",
"build": "del \"**/dist\" && vitepress build && cp _redirects .vitepress/dist",
"preview": "vitepress preview"
},
"dependencies": {
"vue": "^3.3.13"
"vue": "^3.4.14"
},
"devDependencies": {
"vite": "^5.0.10",
"vitepress": "1.0.0-rc.32"
"vite": "^5.0.11",
"vitepress": "1.0.0-rc.39"
}
}
8 changes: 7 additions & 1 deletion docs/tokens/color.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,17 @@ blue:
| `$value` | `string` | **Required.** Though the spec limits valid colors to hex, Cobalt allows any color (parsed by [Culori](https://culorijs.org) |
| `$description` | `string` | (Optional) A description of this token and its intended usage. |

## See Also
## See also

Color is a frequently-used base token that can be aliased within the following composite token types:

- [Border (color)](/tokens/border)
- [Gradient](/tokens/gradient)
- [Shadow](/tokens/shadow)
- [Gradient](/tokens/gradient)

## Tips & recommendations

- [Culori](https://culorijs.org/) is the preferred library for working with color. It’s great both as an accurate, complete color science library that can parse & generate any format. But is also easy-to-use for simple color operations and is fast and [lightweight](https://culorijs.org/guides/tree-shaking/) (even on the client).
- Prefer the [OKLCH](https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl) format for declaring colors ([why?](https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl)). It’s not only a [futureproof standard](https://www.w3.org/TR/css-color-4/#ok-lab); it also allows for better color manipulation than sRGB/hex and is more perceptually-even.
- To generate accessible color ramps, give [Leonardo](https://leonardocolor.io/) a try.
5 changes: 5 additions & 0 deletions docs/tokens/cubic-bezier.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,8 @@ easeInOutCubic:
## See also

- A Cubic Bézier can be used within a [transition token](/tokens/transition).

## Tips & recommendations

- For a list of common easing functions, refer to [easings.net](https://easings.net/).
- For most UI animations, prefer [ease-out curves](https://pow.rs/blog/animation-easings/), though in some instances linear or ease-in curves help ([guide](https://pow.rs/blog/animation-easings/)).
4 changes: 1 addition & 3 deletions docs/tokens/custom.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ title: Custom Tokens

# Custom Tokens

Any token type currently not part of the Design Token Community Group format (DTCG) can be created as a custom token.

However, most Cobalt plugins will throw an error on an unknown type unless you write code that can handle it. The CSS and Sass plugins have a `transformer` option for you to do this.
Any token type currently not part of the Design Token Community Group format (DTCG) can be created as a custom token. However, most Cobalt plugins will throw an error on an unknown type unless you write code that can handle it (the CSS and Sass plugins have a [transform](/integrations/css#transforming-values) option for you to do this).

If your custom types are complex enough, you can also [write your own plugin](/advanced/plugin-api) for Cobalt (which is easier than some may think!).

Expand Down
7 changes: 6 additions & 1 deletion docs/tokens/dimension.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ Dimension is one of the most versatile token types, and can be used for:

As such, organizing dimension tokens properly (and setting good `$description`s) is important!

## See Also
## See also

Note that dimensions **must have units.** To specify a number without units, see [number](/tokens/number).

## Tips & recommendations

- Prefer `rem`s over `px` whenever possible. It’s not only [more accessible](https://www.joshwcomeau.com/css/surprising-truth-about-pixels-and-accessibility/#accessibility-considerations-5), it’s also easier to enforce consistent grids & spacing (e.g. `13px` doesn’t stand out as much as `0.8125rem`).
- Prefer unitless [numbers](/tokens/number) for line heights.
4 changes: 4 additions & 0 deletions docs/tokens/duration.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ moderate:
## See also

- Duration can also be used within a [transition token](/tokens/transition).

## Tips & recommendations

- Most UI animations should exist between `100ms` – `1s` ([source](https://www.nngroup.com/articles/response-times-3-important-limits/)), ideally on the faster end. Any faster and it seems glitchy or unintentional; any slower and it feels unresponsive.
10 changes: 7 additions & 3 deletions docs/tokens/font-family.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ with-fallbacks:

## See also

- [Typography tokens](/tokens/typography)
- Font Family is a part of the [Typography](/tokens/typography) type

```
## Tips & recommendations

```
- The following universal fallback font stack is recommended for your base typography tokens:
```
-apple-system, BlinkMacSystemFont, Segoe UI, Noto Sans, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji`
```
This falls back to system fonts (if a glyph isn’t available) and enables emojis in every OS.
2 changes: 1 addition & 1 deletion docs/tokens/font-weight.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@ A font weight can be a number from `1` (lightest) – `999` (heaviest), but the

## See also

- [Typography tokens](/tokens/typography)
- Font Family is a part of the [Typography](/tokens/typography) type
5 changes: 5 additions & 0 deletions docs/tokens/gradient.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,8 @@ rainbow:
## Notes

- This token is currently missing information on whether this is a [linear](https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient), [radial](https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/radial-gradient), or [conic](https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/conic-gradient) gradient. In most Cobalt plugins, `linear` gradient is assumed.

## Tips & recommendations

- [Culori](https://culorijs.org/) is the preferred library for working with color. It’s great both as an accurate, complete color science library that can parse & generate any format. But is also easy-to-use for simple color operations and is fast and [lightweight](https://culorijs.org/guides/tree-shaking/) (even on the client).
- Prefer the [OKLCH](https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl) format for declaring colors ([why?](https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl)). It’s not only a [futureproof standard](https://www.w3.org/TR/css-color-4/#ok-lab); it also allows for better color manipulation than sRGB/hex and is more perceptually-even.
4 changes: 0 additions & 4 deletions docs/tokens/link.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,3 @@ Assets such as icons, images, and logos are a critical part of any design system
There’s also the image optimization plugin (coming soon) that can optimize image and icon assets.

Refer to each plugin’s documentation to learn what special features are available for Link token types.

```

```
4 changes: 4 additions & 0 deletions docs/tokens/shadow.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ shadow-md:
| `$type` | `string` | **Required.** `"shadow"` |
| `$value` | `object` | **Required.** Specify [`offsetX`](/tokens/dimension), [`offsetY`](/tokens/dimension), [`blur`](/tokens/dimension), [`spread`](/tokens/dimension), and [`color`](/tokens/color) to form a shadow. `color` is the only required prop. |
| `$description` | `string` | (Optional) A description of this token and its intended usage. |

## Tips & recommendations

- For smoother shadows, try [layering multiple](https://shadows.brumm.af/).
8 changes: 6 additions & 2 deletions docs/tokens/typography.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A composite type combining [fontFamily](/tokens/font-family), [dimension](/token
"$type": "typography",
"$value": {
"fontFamily": ["Helvetica", "-system-ui", "sans-serif"],
"fontSize": "24px",
"fontSize": "1.5rem",
"fontStyle": "normal",
"fontWeight": 400,
"letterSpacing": 0,
Expand All @@ -33,7 +33,7 @@ bodyText:
- Helvetica
- -system-ui
- sans-serif
fontSize: 24px
fontSize: 1.5rem
fontStyle: normal
fontWeight: 400
letterSpacing: 0
Expand All @@ -48,3 +48,7 @@ bodyText:
| `$type` | `string` | **Required.** `"typography"` |
| `$value` | `object` | **Required.** Specify any typographic CSS properties in _camelCase_ format. Although the spec limits the properties to only a few, Cobalt allows any valid attributes including `letterSpacing`, `fontVariant`, etc. |
| `$description` | `string` | (Optional) A description of this token and its intended usage. |

## Tips & recommendations

- Though the DTCG spec doesn’t technically allow it, declare any/all CSS typography properties on typography tokens. Without these, you couldn’t use things like [variable font properties](https://fonts.google.com/knowledge/introducing_type/introducing_variable_fonts) in your design system. Plugins may simply ignore properties that don’t apply for the given build target.
2 changes: 1 addition & 1 deletion examples/tailwind/tailwind.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
! tailwindcss v3.3.3 | MIT License | https://tailwindcss.com
! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com
*/

/*
Expand Down
54 changes: 13 additions & 41 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,55 +8,27 @@
},
"homepage": "https://cobalt-ui.pages.dev",
"scripts": {
"prebuild": "del-cli \"**/dist\"",
"build": "run-s -s build:utils build:core build:packages",
"build:core": "cd packages/core && pnpm run build",
"build:examples": "run-p -s build:examples:*",
"build:examples:adobe": "cd examples/adobe && pnpm run build:tokens",
"build:examples:apple": "cd examples/apple && pnpm run build:tokens",
"build:examples:github": "cd examples/github && pnpm run build:tokens",
"build:examples:ibm": "cd examples/ibm && pnpm run build:tokens",
"build:examples:salesforce": "cd examples/salesforce && pnpm run build:tokens",
"build:examples:shopify": "cd examples/shopify && pnpm run build:tokens",
"build:utils": "cd packages/utils && pnpm run build",
"build:packages": "run-p -s build-packages:*",
"build-packages:cli": "cd packages/cli && pnpm run build",
"build-packages:plugin-css": "cd packages/plugin-css && pnpm run build && cd ../plugin-sass && pnpm run build && cd ../plugin-tailwind && pnpm run build",
"build-packages:plugin-js": "cd packages/plugin-js && pnpm run build",
"cf:build": "pnpm run build && cd docs && pnpm run build",
"build": "pnpm run -r --if-present --filter \"@cobalt-ui/*\" \"build\"",
"build:docs": "pnpm run -r --if-present \"build\"",
"build:examples": "pnpm run -r --parallel --if-present build:tokens",
"changeset": "changeset",
"dev": "run-p -s dev:*",
"dev:core": "cd packages/core && pnpm run dev",
"dev:cli": "cd packages/cli && pnpm run dev",
"dev:utils": "cd packages/utils && pnpm run dev",
"dev:plugin-css": "cd packages/plugin-css && pnpm run dev",
"dev:plugin-js": "cd packages/plugin-js && pnpm run dev",
"dev:plugin-sass": "cd packages/plugin-sass && pnpm run dev",
"dev:plugin-tw": "cd packages/plugin-tailwind && pnpm run dev",
"dev": "pnpm run -r --parallel --if-present --filter \"@cobalt-ui/*\" dev",
"lint": "eslint \"packages/**/*.{ts,js,cjs,mjs}\"",
"test": "run-p test:*",
"test:cli": "cd packages/cli && pnpm run test",
"test:core": "cd packages/core && pnpm run test",
"test:plugin-css": "cd packages/plugin-css && pnpm run test",
"test:plugin-js": "cd packages/plugin-js && pnpm run test",
"test:plugin-sass": "cd packages/plugin-sass && pnpm run test",
"test:plugin-tw": "cd packages/plugin-tailwind && pnpm run test",
"test:utils": "cd packages/utils && pnpm run test",
"test": "pnpm run -r --if-present --aggregate-output --parallel test",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to literally no one but myself: --parallel on the top level is a good idea. Nested --parallels is a horrible idea (i.e. don’t use within any packages).

"prepublishOnly": "pnpm run build",
"version": "pnpm run build && changeset version && pnpm i --no-frozen-lockfile"
},
"devDependencies": {
"@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.2",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"@changesets/cli": "^2.27.1",
"@typescript-eslint/eslint-plugin": "^6.19.0",
"@typescript-eslint/parser": "^6.19.0",
"del-cli": "^5.1.0",
"eslint": "^8.54.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"js-yaml": "^4.1.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.1.0",
"typescript": "^5.3.2"
"prettier": "^3.2.2",
"typescript": "^5.3.3"
}
}
Loading
Loading