Your first component
Let's walk through the creation of your first component fully built with Pinceau.
<style lang="ts">
css({
'.my-button': {
backgroundColor: '{color.blue.1}',
padding: '{space.4}'
}
})
</style>
As you can see in this code, Pinceau uses <style lang="ts"> blocks and the css() function.
These blocks are parsed by Pinceau to provide both static and runtime CSS features.
That css() function calls is not really running in the runtime context of your app, but instead will smartly split between the CSS that can be static, and the features that relies on runtime stylesheet.
By doing that, Pinceau makes you write CSS in the same language as the rest of your component, but does not ship any styling-related JavaScript to the client, unless you ultimately need it.
A simple button
Let's walk together through the creation of a simple button with Pinceau.
For this button, we have multiple requirements, it must:
- Support all existing and future colors from our theme color palette
- Have
sm,md,lgandxlsizes - Support
--button-primaryand--button-secondarylocal tokens for advanced customization - Have type-safe component properties
- Plays nicely with
MDCsyntax from @nuxt/content
Basic styling
First, let's define the basic styling of our button with the css() function.
You can find --button-primary and --button-secondary, the local tokens for the button palette.
They are then reused in the other properties, as {button.primary} and {button.secondary}.
You can also notice some other tokens, like {radii.lg}, these are coming from the theme.
This example uses @nuxt-themes/tokens as its tokens definition, but can be adapted to any theme.
<template>
<button class="my-button">
<span>
<ContentSlot :use="$slots.default" unwrap="p" />
</span>
</button>
</template>
<style scoped lang="ts">
css({
'.my-button': {
'--button-primary': `{color.primary.600}`,
'--button-secondary': `{color.primary.500}`,
display: 'inline-block',
borderRadius: '{radii.xl}',
transition: '{transition.all}',
color: '{color.white}',
boxShadow: `0 5px 0 {button.primary}, 0 12px 16px {color.dimmed}`,
span: {
display: 'inline-block',
fontFamily: '{typography.font.display}',
borderRadius: '{radii.lg}',
fontSize: '{fontSize.xl}',
lineHeight: '{lead.none}',
transition: '{transition.all}',
backgroundColor: '{button.primary}',
boxShadow: 'inset 0 -1px 1px {color.dark}',
padding: '{space.3} {space.6}'
},
'&:hover': {
span: {
backgroundColor: `{button.secondary}`,
}
},
'&:active': {
span: {
transform: 'translateY({space.1})'
}
}
}
})
</style>
The button uses <ContentSlot> instead of <slot> as it's built to also work with MDC Syntax.
Make it dynamic
That button works fine, but it only supports primary.600 and primary.500 colors.
It's nice because if these tokens gets updated from the tokens.config file, the colors will follow.
But we also would like our button to support all the colors in our palette, like red, teal or blue.
To achieve that, we'll be using Computed Styles with --button-primary and --button-secondary.
<script setup lang="ts">
import type { PinceauTheme } from 'pinceau'
import { computedStyle } from 'pinceau/runtime'
defineProps({
color: computedStyle<keyof PinceauTheme['color']>('red'),
})
</script>
<template>
<button class="my-button">
<span>
<ContentSlot :use="$slots.default" unwrap="p" />
</span>
</button>
</template>
<style scoped lang="ts">
css({
'.my-button': {
'--button-primary': (props) => `{color.${props.color}.600}`,
'--button-secondary': (props) => `{color.${props.color}.500}`,
...previousStyling
}
})
</style>
In that example we added a prop called color to our component:
-
computedStyle()util is a shortcut to define computed styles propskeyof PinceauTheme['color']will be the type of the prop'red'will be the default value
PinceauThemeis a global type that references your theme
We also converted our --button-primary and --button-secondary keys to arrow functions.
These functions are how you express Computed Styles in your css() function.
color is now a type-safe prop that will follow your color key in your tokens.config file.
Responsive sizes
Now that our button plays nicely with our color palette, giving it mulitple sizes?
These multiple sizes could be helpful in multiple contexts:
Giving emphasis in certain situationsFitting for various UI purposesBeing responsive to your Media Queries
Luckily, Variants got you covered for all these usecases and more.
<script setup lang="ts">
import type { PinceauTheme } from 'pinceau'
import { computedStyle } from 'pinceau/runtime'
defineProps({
color: computedStyle<keyof PinceauTheme['color']>('red'),
...variants,
})
</script>
<template>
<button class="my-button">
<span>
<ContentSlot :use="$slots.default" unwrap="p" />
</span>
</button>
</template>
<style scoped lang="ts">
css({
'.my-button': {
...previousStyling
},
variants: {
size: {
sm: {
span: {
padding: '{space.3} {space.6}',
},
},
md: {
span: {
padding: '{space.6} {space.8}'
},
},
lg: {
span: {
padding: '{space.8} {space.12}'
},
},
xl: {
span: {
padding: '{space.12} {space.24}'
},
},
options: {
default: 'sm',
},
},
},
})
</style>
In that example, we added a variants key to our css() function.
We also spread a variants object in our defineProps() function.
This added another type-safe prop to our component, called size.
The size prop will be usable in two ways:
- As a string prop, accepting the keys inside
sizevariant as value:<MyButton size="sm" />
- As an object prop, taking our Media Queries as keys and the keys inside
sizevariant as value:<MyButton :size="{ initial: 'sm', lg: 'lg' }" />
Your component is ready
In these few steps, you've learned how to:
Write your style in TypeScript with<style lang="ts">andcss()- Create some local tokens for your component (
--button-primary,--button-secondary) - Create bindings between components props and CSS properties with Computed Styles
- Create reusable component appearances variations using Variants