A CSS-in-TS framework built to feel like a native Vue feature.
π¨ Start painting β’ π§ Documentation
- Ships 0kb of JS to the client by default
- DX that feels like a native Vue feature
- Design Tokens compatible configuration system
- Fully-typed styling API
- Integrated with Volar
- Plug & play with Nuxt 3, Vitesse, @vitejs/plugin-vue
Pinceau is still under heavy development, if you are missing some parts of the documentation, please open an issue and describe your problem. I'll be happy to help.
npm i pinceau
Nuxt
// nuxt.config.js
export default defineNuxtConfig({
modules: [
'pinceau/nuxt',
],
pinceau: {
...PinceauOptions
}
})
Example: playground/
This module only works with Nuxt 3.
Vite
// vite.config.ts
import Pinceau from 'pinceau/vite'
export default defineConfig({
plugins: [
Pinceau(PinceauOptions),
],
})
Example: playground/
PinceauOptions
export interface PinceauOptions {
/**
* The root directory of your project.
*
* @default process.cwd()
*/
cwd?: string
/**
* The path of your configuration file.
*/
configOrPaths?: ConfigOrPaths
/**
* The path of your configuration file.
*
* @default 'pinceau.config'
*/
configFileName?: string
/**
* A callback called each time your config gets resolved.
*/
configResolved?: (config: PinceauTheme) => void
/**
* The directry in which you store your design tokens.
*
* @default 'tokens'
*/
tokensDir?: string
/**
* The directory in which you want to output the built version of your configuration.
*/
outputDir?: string
/**
* Imports the default CSS reset in the project.
*
* @default true
*/
preflight?: boolean
/**
* Excluded transform paths.
*/
excludes?: string[]
/**
* Included transform paths.
*/
includes?: string[]
/**
* Toggles color .{dark|light} global classes.
*
* If set to class, all @dark and @light clauses will also be generated
* with .{dark|light} classes on <html> tag as a parent selector.
*
* @default 'class'
*/
colorSchemeMode?: 'media' | 'class'
}
Volar
If you want to have all autocomplete and TypeScript powered features, you need to setup Volar in your IDE.
That also means these features sadly won't work in the StackBlitz playground, unless they provide support for it at some point.
Once Volar enabled, add the Pinceau plugin to your tsconfig.json
:
{
"vueCompilerOptions": {
"plugins": ["pinceau/volar"]
}
}
Once enabled, be sure to restart your TypeScript server, and enjoy autocompletion!
The configuration file is made to help you injecting all the Design Tokens you want into your app as CSS variables, JS references, or any other format you need or want.
You can decide to follow the suggested theme definition keys, but you also are free to define whatever design tokens you want, with the structure of your choice.
As an example, this configuration helped us at NuxtLabs to create a sync between Figma Tokens and our component library.
π‘ Configuration example
import { defineTheme } from 'pinceau'
export default defineTheme({
screens: {
'xs': { value: '475px' },
'sm': { value: '640px' },
'md': { value: '768px' },
'lg': { value: '1024px' },
'xl': { value: '1280px' },
'2xl': { value: '1536px' },
},
colors: {
primary: {
value: '#B6465F'
},
orange: {
50: {
value: '#ffe9d9',
},
100: {
value: '#ffd3b3',
},
200: {
value: '#ffbd8d',
},
300: {
value: '#ffa666',
},
400: {
value: '#ff9040',
},
500: {
value: '#ff7a1a',
},
600: {
value: '#e15e00',
},
700: {
value: '#a94700',
},
800: {
value: '#702f00',
},
900: {
value: '#381800',
}
}
}
})
π¨ Output example
:root {
--screens-xs: 475px;
--screens-sm: 640px;
--screens-md: 768px;
--screens-lg: 1024px;
--screens-xl: 1280px;
--screens-2xl: 1536px;
--color-primary: #B6465F;
--colors-orange-50: #ffe9d9;
--colors-orange-100: #ffd3b3;
--colors-orange-200: #ffbd8d;
--colors-orange-300: #ffa666;
--colors-orange-400: #ff9040;
--colors-orange-500: #ff7a1a;
--colors-orange-600: #e15e00;
--colors-orange-700: #a94700;
--colors-orange-800: #702f00;
--colors-orange-900: #381800;
}
Out of your configuration file, Pinceau will generate multiple output targets which will provide:
- Type completion in
css()
function for mapped theme tokens - Token paths completion with globally available
$dt()
helper - Globally injected CSS variables
- A lot more for you to discover, and for me to document β¨
This is powered by style-dictionary and runs on style-dictionary-esm.
Pinceau styling API is made to feel like a native Vue API.
To do so, it fully takes advantages of the Vue components <style>
tags, and tries to give it even more super powers.
Pinceau enables lang="ts"
as a valid attribute for <style>
or <style scoped>
tags.
That enables the usage of another internal API, css()
.
<style lang="ts">
css({
div: {
color: '{colors.primary}',
backgroundColor: '{colors.orange.50}'
}
})
</style>
The css()
function has mutliple features:
-
Based on
@stitches/stringify
-
Autocomplete CSS properties from
csstype
-
Autocomplete CSS values from theme design tokens
π‘ Example
<style lang="ts"> css({ color: '{*}', < Will autocomplete from all 'colors' keys, padding: '{*}', < Will autocomplete from 'space' keys }) </style>
That works with all nested keys in theme keys, as long as the values are a valid Design Token.
You also get autocompletion from all browser values from MDN thanks to csstype.
-
Autocomplete root keys from your
<template>
elementsπ‘ Example
<template> <div> <button> Hello World! </button> </div> </template> <style lang="ts"> css({ '*' < Will autocomplete 'div' | 'button' }) </style>
-
Supports
postcss-nested
syntaxπ‘ Example
<style lang="ts"> css({ '.phone': { '&_title': { width: '500px', 'body.is-variant &': { color: 'purple' } } } }) </style>
-
Supports
postcss-custom-properties
syntaxπ‘ Example
<style lang="ts"> css({ '.phone': { '--custom-property': '{colors.primary.500}' } }) </style>
-
Supports all custom features from Vue
<style>
attributesπ‘ Example
<script setup> defineProps({ color: { type: String, required: false, default: $dt('colors.primary.500') } }) const myRef = ref() </script> <style lang="ts"> css({ '.phone': { '&:deep(...)': {}, '&:slotted(...)': {}, '&:global(...)': {}, '--custom-property': 'v-bind(myRef)', color: 'v-bind(color)' // ^ This is autocompleted and shows type errors } }) </style>
These features get exact same autocomplete and type-safety as the would in a regular
<style>
tag.Including
:deep()
,:slotted()
,:global()
andv-bind
. -
Supports
@dark
&@light
helpersπ‘ Example
<style lang="ts"> css({ '.block': { color: 'black', backgroundColor: 'white', '@light': { color: 'grey' }, '@dark': { backgroundColor: 'black' } } }) </style>
-
Supports
@nuxtjs/color-mode
π‘ Example
<style lang="ts"> css({ '@sepia': { color: 'black' } }) </style>
This has same API as
@dark
or@light
, but uses color modes defined in@nuxtjs/color-mode
-
Supports
@mq
helperπ‘ Example
- Configuration
import { defineTheme } from 'pinceau' export default defineTheme({ media: { 'sm': { value: '(min-width: 640px)' }, 'md': { value: '(min-width: 768px)' }, 'lg': { value: '(min-width: 1024px)' }, 'xl': { value: '(min-width: 1280px)' }, '2xl': { value: '(min-width: 1536px)' }, 'rm': { value: '(prefers-reduced-motion: reduce)' }, }, })
- Component
<style lang="ts"> css({ '.block': { width: '100%' '@mq.lg': { width: '50%' }, } }) </style> <style lang="postcss"> .block { width: 100%; @mq.lg { width: 50%; } } </style>
You can add as many screen you need, they will all get autocompleted.
-
Supports
rgba()
conversionsπ‘ Example
<style lang="ts"> css({ '.block': { backgroundColor: 'rgba({any.color.token.path}, 0.8)' } }) </style>
-
Supports
computed styles
π‘ new
π‘ Example
This syntax only works with
<script setup lang="ts">
for now.It is planned to support all
<script>
syntaxes soon.<script setup lang="ts"> import type { PropType } from 'vue' // You must specify a key for props when using Variants const props = defineProps({ color: { type: String as PropType<ThemeKey<'color'>> } }) </script> <template> <div class="block" /> </template> <style lang="ts"> css({ '.block': { backgroundColor: (props) => `{colors.${props.color}`, } }) </style>
-
Supports
variants
systemπ‘ new
π‘ Example
This syntax only works with
<script setup lang="ts">
for now.It is planned to support all
<script>
syntaxes soon.Define your component variants at root of
css()
:<script setup lang="ts"> // You must specify a key for props when using Variants const props = defineProps({ // This part is optional, it provides typings ...variantsProps }) </script> <template> <div class="block" /> </template> <style lang="ts"> css({ '.block': { backgroundColor: 'rgba({colors.primary.500}, 0.8)', }, variants: { transparent: { true: { backgroundColor: 'transparent' }, options: { default: true } }, shadows: { soft: { boxShadow: '{shadows.sm}' }, smooth: { boxShadow: '{shadows.lg}', border: '2px solid {colors.primary.500}' }, heavy: { boxShadow: '{shadows.xl}', border: '4px solid {colors.primary.800}' }, options: { default: { sm: 'soft', lg: 'smooth', xl: 'heavy' } } } } }) </style>
Profit from automatically generated props and typings, and compatibility with all your media queries:
<template> <Block :transparent="{ md: true, xl: false }" shadow="{ sm: 'soft', xl: 'smooth' }" /> </template>
$dt
stands for $designToken
.
In addition to css()
, $dt()
comes with everything you need to use your Design Tokens outside of <style>
.
<script setup>
defineProps({
color: {
type: String,
required: false,
default: $dt('colors.primary')
}
})
const orangeVariable = $dt('colors.orange.500')
</script>
<template>
<div :style="{ backgroundColor: color }">
{{ $dt('colors.primary') }}
</div>
</template>
<style lang="postcss">
div {
color: $dt('colors.orange.900')
}
</style>
$dt()
helper will autocomplete all theme keys anywhere in your app.
$dt()
supports options as a second argument, allowing you not only to grab CSS variables from your token, but the whole definition from a token, or a tree of token.
/**
* The key that will be unwrapped from the design token object.
*
* Supports `nested.key.syntax`.
*
* Can be set to `undefined` to return the whole design token object.
*
* @default 'attributes.variable'
*/
key?: string
/**
* Toggle deep flattening of the design token object to the requested key.
*
* If you query an token path containing mutliple design tokens and want a flat \`key: value\` object.
*
* @default false
*/
flatten?: boolean
const allColors = $dt('colors', { flatten: false, key: undefined })
const orangeColor = $dt('colors.orange', { key: undefined })
This is considered as advanced usage and these options might be subject to changes.
Accessing CSS variables via $dt('token.path')
should stay the same.
Please note that $dt()
acts as a macro, like defineProps
or defineEmits
.
It will be replaced when your component gets transformed by Vite by the static value of the token.
That means the only valid value for $dt()
is a plain string, not a reference to a string.
<script setup>
const test = $dt('colors.primary.500') // β
Valid
const ref = ref('colors.primary.500')
const refTest = $dt(ref.value) // π¨ Invalid
</script>
Pinceau is currently in β‘οΈ active β‘οΈ development.
There is plenty of features to come, including:
-
Proper test suite
- Configuration side relies on style-dictionary-esm which is heavily tested
css()
core API relies on [@stitches/stringify
] which also has tests, as this package maintains local implementation of it, I'll add some more tests to it.- All CSS transforms applied on
.vue
components are built with testing in mind
-
Util properties
π‘ Example
// Theme config defineTheme({ colors: { primary: { light: { value: '#B6465F' }, dark: { value: '#4D1C26' } } }, utils: { surface: (value: ThemeKeys<'colors'>) => ({ backgroundColor: `{colors.${value}.dark}`, borderColor: `{colors.${value}.light}` }) } }) // Component <style lang="ts"> css({ surface: 'primary' })
-
Configuring your own transforms, formats and actions
-
π Documentation website
-
π¨ Online playground
-
Maybe yours? I'm happy to provide guidance and reviews on any PR!
-
And even more! π
- SΓ©bastien Chopin
- NuxtLabs
- Daniel Roe
- Anthony Fu
- Johnson Chu
- @patak
- Serhii Bedrytskyi
- The Stitches Team
This package takes inspiration in a lot of other projects, such as style-dictionary, Stitches, vanilla-extract, unocss.
MIT License Β© 2022-PRESENT YaΓ«l GUILLOUX
βAll you need to paint is a few tools, a little instruction, and a vision in your mind.β β’ Bob Ross