Cut the SASS: How to use inline JavaScript styles for everything
October 05, 2016Today, web development is about building applications, not web pages. CSS and SASS were both great for styling pages, but they’re not well suited for styling applications. This is especially true if you are building with a component-based strategy.
What developers want from a modern styling solution is the ability to support breaking down the app into components that fully encapsulate function, markup and styles, preferably into a single file.
Writing your styles in JS is a big change, so I want to take some time to explain why we’d want to do this. I believe in choosing the right tool for the right job. SASS was certainly the right tool for the time. SASS provided a some great benefits:
- Nesting
- Variables
- Mixins
Nesting was a big benefit because it allowed your styles to reflect your markup. If you had:
.html
<div class="UserList">
<div class="User"></div>
<div class="User"></div>
<div class="User"></div>
</div>
You could do:
.scss
.UserList {
.User {
}
}
This benefit is no longer very useful. We are still very interested in having our styles mirror our markup, but our markup has changed!
Today, using a component based approach, we’d prefer to separate these into two distinct components called UserList
and another called User
. The result is two files, where UserList
would contain a repeating series of User
.
UserList.js
const UserList = () => (
<div>
{
array.map( ()=> (
<User />
));
}
<div>
);
User.js
const User = () => (
<div>
<p>User Name Here</p>
</div>
);
Since we’re breaking our application down into components, we should write our styles using the same strategy. That means we should attempt to co-locate our styles with our component’s markup, so that they’ll be encapsulated together.
One way to accomplish this is to use inline-styles, like the days of old, before Cascading Style Sheets.
<div style="margin:0"></div>
This classic method is very well aligned with a component-based approach! We won’t be using it, because it’s very limiting, but it’s demonstrates the principles we’re looking for in a modern styling solution.
Variables and Mixins
So we’ve talked about SASS’s nesting, what about the other big benefits: Variables and mixins?
Well, it just so happens that another, quite popular, solution exists for those: JavaScript!
Wouldn’t it be great if we can leverage JavaScript’s natural features for var
and function
to give us what we need in a styling solution? If we use JavaScript to write our styles, we gain the flexibility, power, and familiarity we love about JS. Plus! Our entire component can be composed in a single language! And even a single file!
Our Goals
Here’s what we want to do:
- Compose our styles directly alongside markup
import
andexport
variables like colors or breakpoints- Write “dynamic” styles based on state (
open
/closed
menu, etc.)
Here are a few more requirements:
- Needs to cover pseudo states like
:hover
,:before
, and:after
- Must directly support media-queries like
@media (max-width: 600px)
- Allow functions / ternaries ( see dynamic, above )
Side note: explaining the difference between “JS styles” and “CSS in JS”
There are two distinct approaches:
- You can include CSS in your JS
- You can write your CSS using JS
I prefer the second method and it’s what we’ll cover here.
For the other, CSS Modules is a popular solution for the “write CSS in your JS” category. It scopes, or name-spaces, regular CSS you write to a component’s <div></div>
. Name-spacing is something you can do manually with classes or IDs, and isn’t enough reason to use something like this alone. For variable support or mixins, you need to use a CSS “enhancer” like PostCSS or SASS with CSS Modules.
Again, we want to be able to leverage JavaScript for language features including variables, ternaries, or functions. If our solution forces to write some sort of “enhanced” CSS instead of JS, we’re not going to be interested.
There are lots of css-in-js solutions available of both types. Check ‘em out! For now, we’re going to implement what I found works best and solves our goals and requirements.
Let’s get started!
We will be using:
First, we’ll create a simple component, called UserMenu. It’ll have a little user avatar, and a menu that drops down when you click it.
UserMenu.js
import React from 'react';
const UserMenu = () => (
<div>
<p>Andrew Davis</p>
<div>
<button> … </button>
<button> … </button>
</div>
</div>
);
export default UserMenu
And you can use it on your app page like:
import UserMenu from './UserMenu';
…
<div>
<header>
…
<UserMenu />
</header>
<div>
…
</div>
</div>
…
The markup isn’t very important. This is just basic React or even AngularJS with a little ES6 / stateless functional components syntax.
Let’s start styling! Begin by including Aphrodite.
UserMenu.js
import React from 'react';
import { StyleSheet, css } from 'aphrodite';
const UserMenu = () => (
<div>
…
</div>
);
Here, we’re importing the specific functions we need from Aphrodite as the docs instruct.
Note on imports: We’re importing specific references with the import {bar} from ‘foo’
syntax. Read more on MDN import documentation
UserMenu.js
import React from 'react';
import { StyleSheet, css } from 'aphrodite';
const UserMenu = () => (
<div className={css(styles.foo)}>
This background is now red.
</div>
);
const styles = StyleSheet.create({
foo: {
backgroundColor: "#FF0000",
},
});
Now we’ve written a const
styles
and use StyleSheet.create()
with our styles object inside. This is from the Aphrodite documentation. Aphrodite injects a <style>
tag into your document and inserts the css for .foo_1l7kpei
into that tag. It’s properly namespaced as well.
We’ve written backgroundColor
instead of background-color
. All of your CSS properties must be camelCased
in JS! Like marginLeft
or fontSize
or justifyContent
. It’s actually really easy to get used to, especially if you write as much JavaScript as I do!
That’s the very basics.
Now, instead of a red hex color, we want to use a branded color. We can use JavasScript variables. Let’s set up a colors.js
file. This is the entire file:
colors.js
export const brandRed = '#D03027';
export const brandGray = '#CFCCCF';
export const brandBlue = '#018BBB';
Our color variables are all being exported from this file. We’re using const
instead of var
, because the values won’t be re-assigned. Now we can import the file in our component and use our colors like this:
UserMenu.js
import React from 'react';
import { StyleSheet, css } from 'aphrodite';
import { brandRed } from './colors;
const UserMenu = () => (
<div className={css(styles.foo)}>
This background is now the Brand Red color.
</div>
);
const styles = StyleSheet.create({
foo: {
backgroundColor: brandRed,
},
});
What about the case where we need to use our JS variable in combination with a string? Like with a border
specification? Easy. Just use ES6 template literals.
UserMenu.js
import React from 'react';
import { StyleSheet, css } from 'aphrodite';
import { brandRed } from './colors;
const UserMenu = () => (
<div className={css(style.foo)}>
This box has a nice border.
</div>
);
const styles = StyleSheet.create({
foo: {
border: `1px solid ${brandRed}`,
},
});
There we go, our variable is combined with our string.
What about pseudo-classes? No problem. Aphrodite has great support for pseudo classes like :before
and :after
.
import React from 'react';
import { StyleSheet, css } from 'aphrodite';
import { brandBlue, brandGray } from './colors;
const UserMenu = () => (
<div className={css(styles.wrapper)}>
<p className={css(styles.username)}>Andrew Davis</p>
</div>
);
const styles = StyleSheet.create({
wrapper: {
padding: '12px 24px',
color: brandBlue,
},
username : {
fontSize: '1.2em',
':before': {
content: '""',
display: 'inline-block',
backgroundColor: brandGray,
height: '12px',
width: '12px',
borderRadius: '100%',
},
},
});
Simply nest the pseudo-class, but use a string
as they key name (:before
)! The above example will create a small circle to the left of the user’s name.
Media queries work the same way.
…
const styles = StyleSheet.create({
wrapper: {
padding: '12px 24px',
color: brandBlue,
'@media (max-width: 600px)': {
padding: '6px 12px',
},
},
username : {
…
},
});'
Now we’ve got a breakpoint going! Padding will be reduced below 600px
.
Now, what if we wanted to take it one step further and keep track of our breakpoints across the app? JavaScript!
We’ll create a separate file, write the breakpoints we want to use as strings and assign them to variables.
breakpoints.js
// Matching
export const isMobile = '@media (max-width: 600px)';
export const isTablet = '@media (min-width: 601px) and (max-width: 1000px)';
export const isDesktop = '@media (min-width: 1000px)';
// AtLeast
export const atLeastTablet = '@media (min-width: 601px)';
// Belows
export const belowDesktop = '@media (max-width: 999px)’;
Now we can import a breakpoint from our .js
file and use it as a variable, just like we did with colors.js
.
import { isMobile } from './breakpoints';
…
const styles = StyleSheet.create({
wrapper: {
padding: '12px 24px',
color: brandBlue,
[isMobile]: {
padding: '6px 12px',
},
},
username : {
…
},
})'
The [isMobile]
will be interpolated into the string '@media (max-width: 600px)'
from our breakpoints.js
file and work just like intended.
Let’s do our final implementation. We’re going to put it all together. It’ll include everything we’ve learned.
We’ll also create a state-based style. It’ll display the menu based on if the component is open or closed.
Note: It’s safe to ignore some of the details around receiving the isOpen
variable from a higher level. Just assume it’s being managed elsewhere and that a click on our div
toggles it from true
to false
.
Note 2: It’s important to understand arrow functions in order to read this code. They’re wonderful when you get used to them. They’re essentially functions
, that immediately return
. Arrow functions on MDN.
Here’s our final code: (You can also view a working demo on CodePen)
UserMenu.js
import React from 'react';
import { dispatch } from 'mobx';
// Style imports
import { StyleSheet, css } from 'aphrodite';
import { brandBlue, brandGray } from './colors;
import { isMobile } from './breakpoints';
// Component
const UserMenu = ({ isOpen }) => (
<div className={css(styles().wrapper)} onClick={() => dispatch(…)}>
<p className={css(styles().username)}>Andrew Davis</p>
<div className={css(styles(isOpen).menu}>
<button>…</button>
<button>…</button>
<button>…</button>
</div>
</div>
);
// Styles
const styles = ( isOpen ) => StyleSheet.create({
wrapper: {
padding: '12px 24px',
color: brandBlue,
[isMobile]: {
padding: '6px 12px',
},
},
username : {
fontSize: '1.2em',
':before': {
content: '""',
display: 'block',
…
},
[isMobile]: {
':before': {
display: 'none', // yes, nesting works
},
},
},
menu : {
position: 'absolute',
background: '#fff',
width: '120px',
height: '500px',
display: isOpen ? 'block' : 'none',
}
});
export default UserMenu
The major change here is that we’ve turned our styles
const
into a function that returns a StyleSheet.create
object. It takes isOpen
as an argument from the component. When isOpen
changes, our menu will show and hide.
So, what have we done with this approach? Well, we’re able to encapsulate everything about a fairly complex component in a single file, and in a single language.
Because the implementation is essentially all JavaScript, you can choose how you want to construct your components. All of the patterns JavaScript will allow are available to you.
This means your styling solution can directly reflect your overall application architecture. This method allows you to have full control. You can move these pieces around and fit them together however you think makes the most human-readable and sensible configuration.
In my experience, the best method is to create many small, fully isolated components, most of which are “stateless” or “dumb” components, only receiving data from the top down in what’s called an Immutable Application Architecture.
I prefer these components to encapsulate their markup, styles, and functionality. Maintaining or changing components becomes trivial when there is a single location and a clear dependency tree.
Because these components only have a single concern, rarely do the files get too large. If they do get too complex, you can easily abstract or refactor them into separate components.
Another benefit is that locating code when making updates becomes trivial when everything you need for a component is co-located a single file.
Components composed this way are also easy to share, as you can pass off as a single .js
file. Your consumers can satisfy the import
declarations as they see fit. There’s no reason that import React
couldn’t become import Angular
.
I hope I have demonstrated the power of inline JS styles and how they are able to replace SASS, as well as given some insight into component based development. It is an approach that really works for our team, hope it works for yours!