


Scripting in Style
What's your Vue?


Who?

Senior FED



Core Maintainer

Meetup Organizer

Blogger



2010
2014
2002
1991
1995
2005
2008
2011














2015






The Fear
...of CSS






Chris
Chris
Chris
/* style.css */
.chris {
background:'blue';
}
/* style.css */
.chris {
background:'gold';
}
/* style.css */
.chris {
background:'green';
}
<div class="my-awesome-container">
<button class="chris">
Chris!
</button>
</div>
Chris!
😰
!important


Scaling
Global namespace
Implicit dependencies
Code Maintenance (DEAD code)
Minification
Non deterministic order of source


The (not) old style
OOCSS & SMACSS
BEM
CSS processors
NOT pure CSS
HARD to migrate
NAMING !
STILL GLOBAL
STILL NOT scalable
(that HUGE CSS file!)
CSS frameworks
HARD to customize


Real-time CSS modifiers ?
var()
😍

😱
JavaScript
😰 Performance
😰 Complexity


Scoped SS


Component.vue
JS
Scoped CSS
/*Item.vue*/
<template>
<div class="item__container_card item__container">
<img :src="src" width="200">
<h3 class="title">{{title}}</h3>
</div>
</template>
<script>
export default { //...code }
</script>
<style scoped lang="scss">
.item__container {
/* ..stylings */
&_card {
margin-right: 10px;
margin-bottom: 10px;
width: 220px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
}
.title {
color: #842803;
margin: 0;
padding: 0.8rem 0;
border-top: 1px solid lightgray;
}
</style>
HTML

/*App.vue*/
<template>
<div id="app">
<h1 class="title">The Pokemons</h1>
<item v-bind="pokemon"/>
</div>
</template>
<script>
export default { /*code */ }
</script>

/*App.vue*/
<template>
<div id="app">
<h1 class="title">The Pokemons</h1>
<item v-bind="pokemon"/>
</div>
</template>
<script>
export default { /*code */ }
</script>
<style>
.title {
color: #111;
}
</style>

/*App.vue*/
<template>
<div id="app" class="app">
<h1 class="title">The Pokemons</h1>
<item v-bind="pokemon"/>
</div>
</template>
<script>
export default { /*code */ }
</script>
<style>
.app .title {
color: #111;
}
</style>



Full CSS support
Pre-processor support
Reusable
Random class generator
/*Item.vue*/
<style lang="scss">
//...
</style>
/* OR */
<style lang="less">
//...
</style>
/* Item.vue */
<template>
//...
<h3 class="title">{{title}}</h3>
//...
</template>
<style scoped>
.title {
//...
}
</style>
/* will be compiled to */
<head>
<style>
.title[data-v-6b294924] {
//...
}
</style>
</head>
<body>
/*...*/
<h3 class="title" data-v-6b294924>
/*...*/
</body>
Scoped
Global Extendable
.app .title >>> title[data-v-6b294924]


CSS has fundamental flaws at scale that can be solved by writing styles in JS .
@VJEUX — NOVEMBER 2014


SS-in-JS


Ideal concepts
Scoped CSS
Extendable
Full CSS support
SSR (Static CSS extraction)
Debuggable
Syntax highlighting
Free of dead CSS code
Themes




2015




<template>
<div class="item__container_card item__container">
<img :src="src">
<h3 class="title">{{title}}</h3>
</div>
</template>
// Will become
<template>
<div :class="[this.item.container, this.item.container_card]">
<img :src="src">
<h3 :class="description.title">{{title}}</h3>
</div>
</template>
<style scoped lang="scss">
.item__container {
/*...*/
}
.title {
/*...*/
}
</style>
//Will become
<style module="item" lang="scss">
.container {
/* Same as before */
}
</style>
<style module="description">
.title {
/* Same as before */
</style>


/*App.vue*/
//...
<style module="theme" lang="scss">
$primary-color: #B4DC47;
$light-orange: #fc9e2d;
$purple: rgb(76, 76, 111);
$dark-purple: #111;
$pink: pink;
:export {
colors: {
default: $primary-color;
lightorange: $light-orange;
purple: $purple;
darkpurple: $dark-purple;
pink: $pink;
}
}
</style>
/*App.vue*/
<script>
//...
provide() {
const theme = {};
Object.defineProperty(theme, "defaultColor", {
enumerable: true,
get: () => this.currentThemeColor
});
return {
theme: theme,
};
},
data() {
return {
colors: this.theme,
currentThemeColor: this.theme.default
};
}
//...
</script>
Theming
/*Layout.vue*/
<template>
<div class="layout__container"
:style="{ background: theme.defaultColor}">
/*...*/
<h4>Current Theme: {{theme.defaultColor}}</h4>
/*...*/
</div>
</template>
<script>
export default {
/*...*/
inject: ["theme"],
/*...*/
}
</script>


All SCOPED CSS Benefits
Theming
Dynamic modifiers
<div
:class="[this.item.container]">
//...
</div>
//Will become
<div
class="_src_components_ModuledItem__container">
//...
</div>


BUT
CSS in JS
not really
It's still


CSS is still READ-ONLY to JS
NO EASY way for theming
NAMING (not name collisions)


THE CSS-in-JS






The challenge
List-view
Grid-view


+
Themes


/*App.vue*/
<script>
const colors = {
default: "#fff",
lightorange: "#fc9e2d",
purple: "rgb(76, 76, 111)",
darkpurple: "#111",
pink: "pink"
};
export default {
name: "App",
components: {
Layout
},
provide() { /*....*/ },
/*...*/
data() {
return {
colors: colors,
currentThemeColor: colors.default
};
}
};
</script>
/*Item.vue*/
<script>
import yiq from "yiq";
import { darken } from "polished";
import styled, { css } from "vue-styled-components";
const itemProps = {
isGrid: Boolean,
themeColor: String
};
const TitleRowStyle = css`/*...*/`;
const StyledItemTitle = styled("h3", itemProps)`
color: ${props => yiq(props.themeColor)};
/*...*/
${props => (props.isGrid ? null : TitleRowStyle)}
`;
const Card = css`/*...*/`;
const Row = css`/*...*/`;
const StyledItem = styled("div", itemProps)`
/*...*/
background: ${props => props.themeColor};
${props => (props.isGrid ? Card : Row)}
&:hover {
background-color: ${props => darken(0.05, props.themeColor)};
cursor: pointer;
}
`;
export default {
inject: ["viewMode", "theme"],
components: { StyledItem, StyledItemTitle },
/*...*/
};
</script>
/*Item.vue*/
<template>
<styled-item
:is-grid="viewMode.isGrid"
:theme-color="theme.defaultColor">
<img :src="src">
<styled-item-title
:is-grid="viewMode.isGrid"
:theme-color="theme.defaultColor">
{{title}}
</styled-item-title>
</styled-item>
</template>



The GOOD


EASY to theme
NO Dead code
Run-time modifier
Extendable
Scoped (100%)
/* ExtendedItem.vue */
import styled from "styled-components";
import Item from './Item'
const ExtendedItem = Item.extend`
background: "blue";
border-color: "blue";
`;


<style module="theme" lang="scss">
/*...*/
:export {
default: #fff;
lightorange: $light-orange;
purple: $purple;
darkpurple: $dark-purple;
pink: $pink;
}
</style>
<script>
export default {
/* ... */
data() {
return {
colors: this.theme,
currentThemeColor: this.theme.default
};
}
}
</script>
<script>
const colors = {
default: "#fff",
lightorange: "#fc9e2d",
purple: "rgb(76, 76, 111)",
darkpurple: "#111",
pink: "pink"
};
export default {
/* ... */
data() {
return {
colors: colors,
currentThemeColor: colors.default
};
}
}
</script>





NO more CSS file!


It's awesome!
Or is it really ? 😏


The NOT so good




❤️
💔


JS
CSS
Parse CSS
Inject to Stylesheet
stylesheet.insertRule
Browser
HTML Paint
load
😮 Accessibility?
NO Cache
download



Box
/* Box.vue */
<template>
<styled-box :size="size"/>
</template>
<script>
import styled from "vue-styled-components";
const boxProps = {
size: String
};
const StyledBox = styled("div", boxProps)`
width: ${props => props.size}px;
height: ${props => props.size}px;
background: grey;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
`;
export default {
components: { StyledBox },
props: boxProps
};
</script>
Input size of box
Interactive Comp






🤯


Security?




CSS-in-JS means
It's still CSS! (not JS)
Learning CSS is still required! 😆


Should we use CSS-in-JS?
Yes, but with caution
So


Easy to theme
NO Dead code
Extendable
Scoped
Styles on run time
Security
Portability
Context switch
Messy code
CSS-in-JS
The Good
The Bad
The Ugly


Portable
No escapsulation
Bundle size
Dead code (somewhere)
Naming
Private to JS code
Load once
No re-render
(Scoped) CSS
The Good
The Bad
The Ugly
Theme-able
Basic scope


"Use the right tool for the right job"
And naming is not a good enough reason...


CSS-in-JS
(Scoped) CSS
When?
CSS modules
CSS consistency between projects
Simple and isolated component
Medium size apps / third-party libs
Security is a concern
Full control over component styling
Theme is critical (IE included)
Support dynamic styling via props
Complex and large apps
IE 11 is not a concern
Basic scope
NO dynamic styling via props


Tachyon CSS

Or just to make life easier
Utilities - FIRST


➕
import tw from 'tailwind.macro';
const StyledTitle = styled('h3')`
${() => tw("text-red-500")}
`;


Resources



Thank you

Scripting in style, What's your Vue? (Toronto 2019)
By Maya Shavin
Scripting in style, What's your Vue? (Toronto 2019)
Built-in CSS Modules and Scoped CSS support eases the process of styling your Vue. But a separate tag is still needed. What about styling directly and dynamically in JavaScript - aka CSS-in-JS and save the pain? Should we, or should we not consider this as a good approach? This is what my talk about. Code sample: https://codesandbox.io/embed/style-with-vue-fzzci
- 1,522