goober, a less than 1KB css-in-js solution.
The Great Shave Off Challenge
Can you shave off bytes from goober? Do it and you're gonna get paid! More info here
Motivation
I've always wondered if you could get a working solution for css-in-js with a smaller footprint. While I was working on a side project I wanted to use styled-components, or more accurately the styled pattern. Looking at the JavaScript bundle sizes, I quickly realized that I would have to include ~12kB(styled-components) or ~11kB(emotion) just so I can use the styled paradigm. So, I embarked on a mission to create a smaller alternative for these well established APIs.
Why the peanuts emoji?
It's a pun on the tagline.
css-in-js at the cost of peanuts! goober
Talks and Podcasts
- React Round Up https://reactroundup.com/wrangle-your-css-in-js-for-peanuts-using-goober-ft-cristian-bote-rru-177
- ReactDay Berlin 2019 https://www.youtube.com/watch?v=k4-AVy3acqk
- PodRocket by LogRocket https://podrocket.logrocket.com/goober
- ngParty https://www.youtube.com/watch?v=XKFvOBDPeB0
Table of contents
Usage
The API is inspired by emotion styled function. Meaning, you call it with your tagName, and it returns a vDOM component for that tag. Note, setup needs to be ran before the styled function is used.
import { h } from 'preact';
import { styled, setup } from 'goober';
// Should be called here, and just once
setup(h);
const Icon = styled('span')`
display: flex;
flex: 1;
color: red;
`;
const Button = styled('button')`
background: dodgerblue;
color: white;
border: ${Math.random()}px solid white;
&:focus,
&:hover {
padding: 1em;
}
.otherClass {
margin: 0;
}
${Icon} {
color: black;
}
`;
Examples
Comparison and tradeoffs
In this section I would like to compare goober, as objectively as I can, with the latest versions of two most well known css-in-js packages: styled-components and emotion.
I've used the following markers to reflect the state of each feature:
- Supported
- Partially supported
- Not supported
Here we go:
| Feature name | Goober | Styled Components | Emotion |
|---|---|---|---|
| Base bundle size | 1.25 kB | 12.6 kB | 7.4 kB |
| Framework agnostic | *3 | ||
| Render with target *1 | |||
css api |
|||
css prop |
|||
styled |
|||
styled.<tag> |
*2 | ||
| default export | |||
as |
|||
.withComponent |
|||
.attrs |
|||
shouldForwardProp |
|||
keyframes |
|||
| Labels | |||
| ClassNames | |||
| Global styles | |||
| SSR | |||
| Theming | |||
| Tagged Templates | |||
| Object styles | |||
| Dynamic styles |
Footnotes
- [1]
goobercan render in any dom target. Meaning you can usegooberto define scoped styles in any context. Really useful for web-components. - [2] Supported only via
babel-plugin-transform-goober - [3] Emotion has a framework-agnostic
cssfunction. See https://emotion.sh/docs/@emotion/css
SSR
You can get the critical CSS for SSR via extractCss. Take a look at this example: CodeSandbox: SSR with Preact and goober and read the full explanation for extractCSS and targets below.
Benchmarks
The results are included inside the build output as well.
Browser
Coming soon!
SSR
The benchmark is testing the following scenario:
import styled from '<packageName>';
// Create the dynamic styled component
const Foo = styled('div')((props) => ({
opacity: props.counter > 0.5 ? 1 : 0,
'@media (min-width: 1px)': {
rule: 'all'
},
'&:hover': {
another: 1,
display: 'space'
}
}));
// Serialize the component
renderToString(<Foo counter={Math.random()} />);
The results are:
goober x 200,437 ops/sec ±1.93% (87 runs sampled)
styled-components@5.2.1 x 12,650 ops/sec ±9.09% (48 runs sampled)
emotion@11.0.0 x 104,229 ops/sec ±2.06% (88 runs sampled)
Fastest is: goober
API
As you can see, goober supports most of the CSS syntax. If you find any issues, please submit a ticket, or open a PR with a fix.
styled(tagName: String | Function, forwardRef?: Function)
@param {String|Function} tagNameThe name of the DOM element you'd like the styles to be applied to@param {Function} forwardRefForward ref function. UsuallyReact.forwardRef@returns {Function}Returns the tag template function.
import { styled } from 'goober';
const Btn = styled('button')`
border-radius: 4px;
`;
Different ways of customizing the styles
Tagged templates functions
import { styled } from 'goober';
const Btn = styled('button')`
border-radius: ${(props) => props.size}px;
`;
<Btn size={20} />;
Function that returns a string
import { styled } from 'goober';
const Btn = styled('button')(
(props) => `
border-radius: ${props.size}px;
`
);
<Btn size={20} />;
JSON/Object
import { styled } from 'goober';
const Btn = styled('button')((props) => ({
borderRadius: props.size + 'px'
}));
<Btn size={20} />;
Arrays
import { styled } from 'goober';
const Btn = styled('button')([
{ color: 'tomato' },
({ isPrimary }) => ({ background: isPrimary ? 'cyan' : 'gray' })
]);
<Btn />; // This will render the `Button` with `background: gray;`
<Btn isPrimary />; // This will render the `Button` with `background: cyan;`
Forward ref function
As goober is JSX library agnostic, you need to pass in the forward ref function for the library you are using. Here's how you do it for React.
const Title = styled('h1', React.forwardRef)`
font-weight: bold;
color: dodgerblue;
`;
setup(pragma: Function, prefixer?: Function, theme?: Function, forwardProps?: Function)
The call to setup() should occur only once. It should be called in the entry file of your project.
Given the fact that react uses createElement for the transformed elements and preact uses h, setup should be called with the proper pragma function. This was added to reduce the bundled size and being able to bundle an esmodule version. At the moment, it's the best tradeoff I can think of.
import React from 'react';
import { setup } from 'goober';
setup(React.createElement);
With prefixer
import React from 'react';
import { setup } from 'goober';
const customPrefixer = (key, value) => `${key}: ${value};\n`;
setup(React.createElement, customPrefixer);
With theme
import React, { createContext, useContext, createElement } from 'react';
import { setup, styled } from 'goober';
const theme = { primary: 'blue' };
const ThemeContext = createContext(theme);
const useTheme = () => useContext(ThemeContext);
setup(createElement, undefined, useTheme);
const ContainerWithTheme = styled('div')`
color: ${(props) => props.theme.primary};
`;
With forwardProps
The forwardProps function offers a way to achieve the same shouldForwardProps functionality as emotion and styled-components (with transient props) offer. The difference here is that the function receives the whole props and you are in charge of removing the props that should not end up in the DOM.
This is a super useful functionality when paired with theme object, variants, or any other customisation one might need.
import React from 'react';
import { setup, styled } from 'goober';
setup(React.createElement, undefined, undefined, (props) => {
for (let prop in props) {
// Or any other conditions.
// This could also check if this is a dev build and not remove the props
if (prop === 'size') {
delete props[prop];
}
}
});
The functionality of "transient props" (with a "$" prefix) can be implemented as follows:
import React from 'react';
import { setup, styled } from 'goober';
setup(React.createElement, undefined, undefined, (props) => {
for (let prop in props) {
if (prop[0] === '
Alternatively you can use goober/should-forward-prop addon to pass only the filter function and not have to deal with the full props object.
import React from 'react';
import { setup, styled } from 'goober';
import { shouldForwardProp } from 'goober/should-forward-prop';
setup(
React.createElement,
undefined,
undefined,
// This package accepts a `filter` function. If you return false that prop
// won't be included in the forwarded props.
shouldForwardProp((prop) => {
return prop !== 'size';
})
);
css(taggedTemplate)
@returns {String} Returns the className.
To create a className, you need to call css with your style rules in a tagged template.
import { css } from "goober";
const BtnClassName = css`
border-radius: 4px;
`;
// vanilla JS
const btn = document.querySelector("#btn");
// BtnClassName === 'g016232'
btn.classList.add(BtnClassName);
// JSX
// BtnClassName === 'g016232'
const App => <button className={BtnClassName}>click</button>
Different ways of customizing css
Passing props to css tagged templates
import { css } from 'goober';
// JSX
const CustomButton = (props) => (
<button
className={css`
border-radius: ${props.size}px;
`}
>
click
</button>
);
Using css with JSON/Object
import { css } from 'goober';
const BtnClassName = (props) =>
css({
background: props.color,
borderRadius: props.radius + 'px'
});
Notice: using css with object can reduce your bundle size.
We can also declare styles at the top of the file by wrapping css into a function that we call to get the className.
import { css } from 'goober';
const BtnClassName = (props) => css`
border-radius: ${props.size}px;
`;
// vanilla JS
// BtnClassName({size:20}) -> g016360
const btn = document.querySelector('#btn');
btn.classList.add(BtnClassName({ size: 20 }));
// JSX
// BtnClassName({size:20}) -> g016360
const App = () => <button className={BtnClassName({ size: 20 })}>click</button>;
The difference between calling css directly and wrapping into a function is the timing of its execution. The former is when the component(file) is imported, the latter is when it is actually rendered.
If you use extractCSS for SSR, you may prefer to use the latter, or the styled API to avoid inconsistent results.
targets
By default, goober will append a style tag to the <head> of a document. You might want to target a different node, for instance, when you want to use goober with web components (so you'd want it to append style tags to individual shadowRoots). For this purpose, you can .bind a new target to the styled and css methods:
import * as goober from 'goober';
const target = document.getElementById('target');
const css = goober.css.bind({ target: target });
const styled = goober.styled.bind({ target: target });
If you don't provide a target, goober always defaults to <head> and in environments without a DOM (think certain SSR solutions), it will just use a plain string cache to store generated styles which you can extract with extractCSS(see below).
extractCss(target?)
@returns {String}
Returns the <style> tag that is rendered in a target and clears the style sheet. Defaults to <head>.
const { extractCss } = require('goober');
// After your app has rendered, just call it:
const styleTag = `<style id="_goober">${extractCss()}</style>`;
// Note: To be able to `hydrate` the styles you should use the proper `id` so `goober` can pick it up and use it as the target from now on
createGlobalStyles
To define your global styles you need to create a GlobalStyles component and use it as part of your tree. The createGlobalStyles is available at goober/global addon.
import { createGlobalStyles } from 'goober/global';
const GlobalStyles = createGlobalStyles`
html,
body {
background: light;
}
* {
box-sizing: border-box;
}
`;
export default function App() {
return (
<div id="root">
<GlobalStyles />
<Navigation>
<RestOfYourApp>
</div>
)
}
How about using glob function directly?
Before the global addon, goober/global, there was a method named glob that was part of the main package that would do the same thing, more or less. Having only that method to define global styles usually led to missing global styles from the extracted css, since the pattern did not enforce the evaluation of the styles at render time. The glob method is still exported from goober/global, in case you have a hard dependency on it. It still has the same API:
import { glob } from 'goober';
glob`
html,
body {
background: light;
}
* {
box-sizing: border-box;
}
`;
keyframes
keyframes is a helpful method to define reusable animations that can be decoupled from the main style declaration and shared across components.
import { keyframes } from 'goober';
const rotate = keyframes`
from, to {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
`;
const Wicked = styled('div')`
background: tomato;
color: white;
animation: ${rotate} 1s ease-in-out;
`;
shouldForwardProp
To implement the shouldForwardProp without the need to provide the full loop over props you can use the goober/should-forward-prop addon.
import { h } from 'preact';
import { setup } from 'goober';
import { shouldForwardProp } from 'goober/should-forward-prop';
setup(
h,
undefined,
undefined,
shouldForwardProp((prop) => {
// Do NOT forward props that start with `
goober, a less than 1KB css-in-js solution.
The Great Shave Off Challenge
Can you shave off bytes from goober? Do it and you're gonna get paid! More info here
Motivation
I've always wondered if you could get a working solution for css-in-js with a smaller footprint. While I was working on a side project I wanted to use styled-components, or more accurately the __INLINE_CODE_0__ pattern. Looking at the JavaScript bundle sizes, I quickly realized that I would have to include ~12kB(styled-components) or ~11kB(emotion) just so I can use the __INLINE_CODE_1__ paradigm. So, I embarked on a mission to create a smaller alternative for these well established APIs.
Why the peanuts emoji?
It's a pun on the tagline.
css-in-js at the cost of peanuts!
goober
Talks and Podcasts
- React Round Up https://reactroundup.com/wrangle-your-css-in-js-for-peanuts-using-goober-ft-cristian-bote-rru-177
- ReactDay Berlin 2019 https://www.youtube.com/watch?v=k4-AVy3acqk
- PodRocket by LogRocket https://podrocket.logrocket.com/goober
- ngParty https://www.youtube.com/watch?v=XKFvOBDPeB0
Table of contents
Usage
The API is inspired by emotion __INLINE_CODE_2__ function. Meaning, you call it with your __INLINE_CODE_3__, and it returns a vDOM component for that tag. Note, __INLINE_CODE_4__ needs to be ran before the __INLINE_CODE_5__ function is used.
import { h } from 'preact';
import { styled, setup } from 'goober';
// Should be called here, and just once
setup(h);
const Icon = styled('span')`
display: flex;
flex: 1;
color: red;
`;
const Button = styled('button')`
background: dodgerblue;
color: white;
border: ${Math.random()}px solid white;
&:focus,
&:hover {
padding: 1em;
}
.otherClass {
margin: 0;
}
${Icon} {
color: black;
}
`;
Examples
Comparison and tradeoffs
In this section I would like to compare goober, as objectively as I can, with the latest versions of two most well known css-in-js packages: styled-components and emotion.
I've used the following markers to reflect the state of each feature:
- Supported
- Partially supported
- Not supported
Here we go:
Feature name
Goober
Styled Components
Emotion
Base bundle size
1.25 kB
12.6 kB
7.4 kB
Framework agnostic
*3
Render with target *1
__INLINE_CODE_6__ api
__INLINE_CODE_7__ prop
__INLINE_CODE_8__
__INLINE_CODE_9__
*2
default export
__INLINE_CODE_10__
__INLINE_CODE_11__
__INLINE_CODE_12__
__INLINE_CODE_13__
__INLINE_CODE_14__
Labels
ClassNames
Global styles
SSR
Theming
Tagged Templates
Object styles
Dynamic styles
Footnotes
- [1] __INLINE_CODE_15__ can render in any dom target. Meaning you can use __INLINE_CODE_16__ to define scoped styles in any context. Really useful for web-components.
- [2] Supported only via __INLINE_CODE_17__
- [3] Emotion has a framework-agnostic __INLINE_CODE_18__ function. See https://emotion.sh/docs/@emotion/css
SSR
You can get the critical CSS for SSR via __INLINE_CODE_19__. Take a look at this example: CodeSandbox: SSR with Preact and goober and read the full explanation for __INLINE_CODE_20__ and __INLINE_CODE_21__ below.
Benchmarks
The results are included inside the build output as well.
Browser
Coming soon!
SSR
The benchmark is testing the following scenario:
import styled from '<packageName>';
// Create the dynamic styled component
const Foo = styled('div')((props) => ({
opacity: props.counter > 0.5 ? 1 : 0,
'@media (min-width: 1px)': {
rule: 'all'
},
'&:hover': {
another: 1,
display: 'space'
}
}));
// Serialize the component
renderToString(<Foo counter={Math.random()} />);
The results are:
goober x 200,437 ops/sec ±1.93% (87 runs sampled)
styled-components@5.2.1 x 12,650 ops/sec ±9.09% (48 runs sampled)
emotion@11.0.0 x 104,229 ops/sec ±2.06% (88 runs sampled)
Fastest is: goober
API
As you can see, goober supports most of the CSS syntax. If you find any issues, please submit a ticket, or open a PR with a fix.
__INLINE_CODE_22__
- __INLINE_CODE_23__ The name of the DOM element you'd like the styles to be applied to
- __INLINE_CODE_24__ Forward ref function. Usually __INLINE_CODE_25__
- __INLINE_CODE_26__ Returns the tag template function.
import { styled } from 'goober';
const Btn = styled('button')`
border-radius: 4px;
`;
Different ways of customizing the styles
Tagged templates functions
import { styled } from 'goober';
const Btn = styled('button')`
border-radius: ${(props) => props.size}px;
`;
<Btn size={20} />;
Function that returns a string
import { styled } from 'goober';
const Btn = styled('button')(
(props) => `
border-radius: ${props.size}px;
`
);
<Btn size={20} />;
JSON/Object
import { styled } from 'goober';
const Btn = styled('button')((props) => ({
borderRadius: props.size + 'px'
}));
<Btn size={20} />;
Arrays
import { styled } from 'goober';
const Btn = styled('button')([
{ color: 'tomato' },
({ isPrimary }) => ({ background: isPrimary ? 'cyan' : 'gray' })
]);
<Btn />; // This will render the `Button` with `background: gray;`
<Btn isPrimary />; // This will render the `Button` with `background: cyan;`
Forward ref function
As goober is JSX library agnostic, you need to pass in the forward ref function for the library you are using. Here's how you do it for React.
const Title = styled('h1', React.forwardRef)`
font-weight: bold;
color: dodgerblue;
`;
__INLINE_CODE_27__
The call to __INLINE_CODE_28__ should occur only once. It should be called in the entry file of your project.
Given the fact that __INLINE_CODE_29__ uses __INLINE_CODE_30__ for the transformed elements and __INLINE_CODE_31__ uses __INLINE_CODE_32__, __INLINE_CODE_33__ should be called with the proper pragma function. This was added to reduce the bundled size and being able to bundle an esmodule version. At the moment, it's the best tradeoff I can think of.
import React from 'react';
import { setup } from 'goober';
setup(React.createElement);
With prefixer
import React from 'react';
import { setup } from 'goober';
const customPrefixer = (key, value) => `${key}: ${value};\n`;
setup(React.createElement, customPrefixer);
With theme
import React, { createContext, useContext, createElement } from 'react';
import { setup, styled } from 'goober';
const theme = { primary: 'blue' };
const ThemeContext = createContext(theme);
const useTheme = () => useContext(ThemeContext);
setup(createElement, undefined, useTheme);
const ContainerWithTheme = styled('div')`
color: ${(props) => props.theme.primary};
`;
With forwardProps
The __INLINE_CODE_34__ function offers a way to achieve the same __INLINE_CODE_35__ functionality as emotion and styled-components (with transient props) offer. The difference here is that the function receives the whole props and you are in charge of removing the props that should not end up in the DOM.
This is a super useful functionality when paired with theme object, variants, or any other customisation one might need.
import React from 'react';
import { setup, styled } from 'goober';
setup(React.createElement, undefined, undefined, (props) => {
for (let prop in props) {
// Or any other conditions.
// This could also check if this is a dev build and not remove the props
if (prop === 'size') {
delete props[prop];
}
}
});
The functionality of "transient props" (with a "$" prefix) can be implemented as follows:
import React from 'react';
import { setup, styled } from 'goober';
setup(React.createElement, undefined, undefined, (props) => {
for (let prop in props) {
if (prop[0] === '
Alternatively you can use __INLINE_CODE_36__ addon to pass only the filter function and not have to deal with the full __INLINE_CODE_37__ object.
import React from 'react';
import { setup, styled } from 'goober';
import { shouldForwardProp } from 'goober/should-forward-prop';
setup(
React.createElement,
undefined,
undefined,
// This package accepts a `filter` function. If you return false that prop
// won't be included in the forwarded props.
shouldForwardProp((prop) => {
return prop !== 'size';
})
);
__INLINE_CODE_38__
- __INLINE_CODE_39__ Returns the className.
To create a className, you need to call __INLINE_CODE_40__ with your style rules in a tagged template.
import { css } from "goober";
const BtnClassName = css`
border-radius: 4px;
`;
// vanilla JS
const btn = document.querySelector("#btn");
// BtnClassName === 'g016232'
btn.classList.add(BtnClassName);
// JSX
// BtnClassName === 'g016232'
const App => <button className={BtnClassName}>click</button>
Different ways of customizing __INLINE_CODE_41__
Passing props to __INLINE_CODE_42__ tagged templates
import { css } from 'goober';
// JSX
const CustomButton = (props) => (
<button
className={css`
border-radius: ${props.size}px;
`}
>
click
</button>
);
Using __INLINE_CODE_43__ with JSON/Object
import { css } from 'goober';
const BtnClassName = (props) =>
css({
background: props.color,
borderRadius: props.radius + 'px'
});
Notice: using __INLINE_CODE_44__ with object can reduce your bundle size.
We can also declare styles at the top of the file by wrapping __INLINE_CODE_45__ into a function that we call to get the className.
import { css } from 'goober';
const BtnClassName = (props) => css`
border-radius: ${props.size}px;
`;
// vanilla JS
// BtnClassName({size:20}) -> g016360
const btn = document.querySelector('#btn');
btn.classList.add(BtnClassName({ size: 20 }));
// JSX
// BtnClassName({size:20}) -> g016360
const App = () => <button className={BtnClassName({ size: 20 })}>click</button>;
The difference between calling __INLINE_CODE_46__ directly and wrapping into a function is the timing of its execution. The former is when the component(file) is imported, the latter is when it is actually rendered.
If you use __INLINE_CODE_47__ for SSR, you may prefer to use the latter, or the __INLINE_CODE_48__ API to avoid inconsistent results.
__INLINE_CODE_49__
By default, goober will append a style tag to the __INLINE_CODE_50__ of a document. You might want to target a different node, for instance, when you want to use goober with web components (so you'd want it to append style tags to individual shadowRoots). For this purpose, you can __INLINE_CODE_51__ a new target to the __INLINE_CODE_52__ and __INLINE_CODE_53__ methods:
import * as goober from 'goober';
const target = document.getElementById('target');
const css = goober.css.bind({ target: target });
const styled = goober.styled.bind({ target: target });
If you don't provide a target, goober always defaults to __INLINE_CODE_54__ and in environments without a DOM (think certain SSR solutions), it will just use a plain string cache to store generated styles which you can extract with __INLINE_CODE_55__(see below).
__INLINE_CODE_56__
- __INLINE_CODE_57__
Returns the __INLINE_CODE_58__ tag that is rendered in a target and clears the style sheet. Defaults to __INLINE_CODE_59__.
const { extractCss } = require('goober');
// After your app has rendered, just call it:
const styleTag = `<style id="_goober">${extractCss()}</style>`;
// Note: To be able to `hydrate` the styles you should use the proper `id` so `goober` can pick it up and use it as the target from now on
__INLINE_CODE_60__
To define your global styles you need to create a __INLINE_CODE_61__ component and use it as part of your tree. The __INLINE_CODE_62__ is available at __INLINE_CODE_63__ addon.
import { createGlobalStyles } from 'goober/global';
const GlobalStyles = createGlobalStyles`
html,
body {
background: light;
}
* {
box-sizing: border-box;
}
`;
export default function App() {
return (
<div id="root">
<GlobalStyles />
<Navigation>
<RestOfYourApp>
</div>
)
}
How about using __INLINE_CODE_64__ function directly?
Before the global addon, __INLINE_CODE_65__, there was a method named __INLINE_CODE_66__ that was part of the main package that would do the same thing, more or less. Having only that method to define global styles usually led to missing global styles from the extracted css, since the pattern did not enforce the evaluation of the styles at render time. The __INLINE_CODE_67__ method is still exported from __INLINE_CODE_68__, in case you have a hard dependency on it. It still has the same API:
import { glob } from 'goober';
glob`
html,
body {
background: light;
}
* {
box-sizing: border-box;
}
`;
__INLINE_CODE_69__
__INLINE_CODE_70__ is a helpful method to define reusable animations that can be decoupled from the main style declaration and shared across components.
import { keyframes } from 'goober';
const rotate = keyframes`
from, to {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
`;
const Wicked = styled('div')`
background: tomato;
color: white;
animation: ${rotate} 1s ease-in-out;
`;
__INLINE_CODE_71__
To implement the __INLINE_CODE_72__ without the need to provide the full loop over __INLINE_CODE_73__ you can use the __INLINE_CODE_74__ addon.
symbol
return prop['0'] !== '
Integrations
Babel plugin
You're in love with the styled.div syntax? Fear no more! We got you covered with a babel plugin that will take your lovely syntax from styled.tag and translate it to goober's styled("tag") call.
npm i --save-dev babel-plugin-transform-goober
# or
yarn add --dev babel-plugin-transform-goober
Visit the package in here for more info (https://github.com/cristianbote/goober/tree/master/packages/babel-plugin-transform-goober)
Babel macro plugin
A babel-plugin-macros macro for [goober][goober], rewriting styled.div syntax to styled('div') calls.
Usage
Once you've configured babel-plugin-macros, change your imports from goober to goober/macro.
Now you can create your components using styled.* syntax:.
import { styled } from 'goober/macro';
const Button = styled.button`
margin: 0;
padding: 1rem;
font-size: 1rem;
background-color: tomato;
`;
Next.js
Want to use goober with Next.js? We've got you covered! Follow the example below or from the main examples directory.
npx create-next-app --example with-goober with-goober-app
# or
yarn create next-app --example with-goober with-goober-app
Gatsby
Want to use goober with Gatsby? We've got you covered! We have our own plugin to deal with styling your Gatsby projects.
npm i --save goober gatsby-plugin-goober
# or
yarn add goober gatsby-plugin-goober
Preact CLI plugin
If you use Goober with Preact CLI, you can use preact-cli-goober-ssr
npm i --save-dev preact-cli-goober-ssr
# or
yarn add --dev preact-cli-goober-ssr
# preact.config.js
const gooberPlugin = require('preact-cli-goober-ssr')
export default (config, env) => {
gooberPlugin(config, env)
}
When you build your Preact application, this will run extractCss on your pre-rendered pages and add critical styles for each page.
CSS Prop
You can use a custom css prop to pass in styles on HTML elements with this Babel plugin.
Installation:
npm install --save-dev @agney/babel-plugin-goober-css-prop
List the plugin in .babelrc:
{
"plugins": [
"@agney/babel-plugin-goober-css-prop"
]
}
Usage:
<main
css={`
display: flex;
min-height: 100vh;
justify-content: center;
align-items: center;
`}
>
<h1 css="color: dodgerblue">Goober</h1>
</main>
Features
- Basic CSS parsing
- Nested rules with pseudo selectors
- Nested styled components
- Extending Styles
- Media queries (@media)
- Keyframes (@keyframes)
- Smart (lazy) client-side hydration
- Styling any component
- via
const Btn = ({className}) => {...}; const TomatoBtn = styled(Btn)`color: tomato;`
- Vanilla (via
css function)
-
globalStyle (via glob) so one would be able to create global styles
- target/extract from elements other than
<head>
- vendor prefixing
Content Security Policy (CSP)
goober supports Content Security Policy nonces for inline styles. Set window.__nonce__ before loading the library:
<script nonce="your-nonce-here">
window.__nonce__ = 'your-nonce-here';
</script>
The nonce will be added to goober's <style> element.
Sharing style
There are a couple of ways to effectively share/extend styles across components.
Extending
You can extend the desired component that needs to be enriched or overwritten with another set of css rules.
import { styled } from 'goober';
// Let's declare a primitive for our styled component
const Primitive = styled('span')`
margin: 0;
padding: 0;
`;
// Later on we could get the primitive shared styles and also add our owns
const Container = styled(Primitive)`
padding: 1em;
`;
Using as prop
Another helpful way to extend a certain component is with the as property. Given our example above we could modify it like:
import { styled } from 'goober';
// Our primitive element
const Primitive = styled('span')`
margin: 0;
padding: 0;
`;
const Container = styled('div')`
padding: 1em;
`;
// At composition/render time
<Primitive as={'div'} /> // <div class="go01234" />
// Or using the `Container`
<Primitive as={Container} /> // <div class="go01234 go56789" />
Autoprefixer
Autoprefixing is a helpful way to make sure the generated css will work seamlessly on the whole spectrum of browsers. With that in mind, the core goober package can't hold that logic to determine the autoprefixing needs, so we added a new package that you can choose to address them.
npm install goober
# or
yarn add goober
After the main package is installed it's time to bootstrap goober with it:
import { setup } from 'goober';
import { prefix } from 'goober/prefixer';
// Bootstrap goober
setup(React.createElement, prefix);
And voilà! It is done!
TypeScript
goober comes with type definitions build in, making it easy to get started in TypeScript straight away.
Prop Types
If you're using custom props and wish to style based on them, you can do so as follows:
interface Props {
size: number;
}
styled('div')<Props>`
border-radius: ${(props) => props.size}px;
`;
// This also works!
styled<Props>('div')`
border-radius: ${(props) => props.size}px;
`;
Extending Theme
If you're using a custom theme and want to add types to it, you can create a declaration file at the base of your project.
// goober.d.t.s
import 'goober';
declare module 'goober' {
export interface DefaultTheme {
colors: {
primary: string;
};
}
}
You should now have autocompletion for your theme.
const ThemeContainer = styled('div')`
background-color: ${(props) => props.theme.colors.primary};
`;
Browser support
goober supports all major browsers (Chrome, Edge, Firefox, Safari).
To support IE 11 and older browsers, make sure to use a tool like Babel to transform your code into code that works in the browsers you target.
Contributing
Feel free to try it out and checkout the examples. If you wanna fix something feel free to open a issue or a PR.
Backers
Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website.

) {
delete props[prop];
}
}
});
Alternatively you can use __INLINE_CODE_36__ addon to pass only the filter function and not have to deal with the full __INLINE_CODE_37__ object.
__CODE_BLOCK_14__
__INLINE_CODE_38__
- __INLINE_CODE_39__ Returns the className.
To create a className, you need to call __INLINE_CODE_40__ with your style rules in a tagged template.
__CODE_BLOCK_15__
Different ways of customizing __INLINE_CODE_41__
Passing props to __INLINE_CODE_42__ tagged templates
__CODE_BLOCK_16__
Using __INLINE_CODE_43__ with JSON/Object
__CODE_BLOCK_17__
Notice: using __INLINE_CODE_44__ with object can reduce your bundle size.
We can also declare styles at the top of the file by wrapping __INLINE_CODE_45__ into a function that we call to get the className.
__CODE_BLOCK_18__
The difference between calling __INLINE_CODE_46__ directly and wrapping into a function is the timing of its execution. The former is when the component(file) is imported, the latter is when it is actually rendered.
If you use __INLINE_CODE_47__ for SSR, you may prefer to use the latter, or the __INLINE_CODE_48__ API to avoid inconsistent results.
__INLINE_CODE_49__
By default, goober will append a style tag to the __INLINE_CODE_50__ of a document. You might want to target a different node, for instance, when you want to use goober with web components (so you'd want it to append style tags to individual shadowRoots). For this purpose, you can __INLINE_CODE_51__ a new target to the __INLINE_CODE_52__ and __INLINE_CODE_53__ methods:
__CODE_BLOCK_19__
If you don't provide a target, goober always defaults to __INLINE_CODE_54__ and in environments without a DOM (think certain SSR solutions), it will just use a plain string cache to store generated styles which you can extract with __INLINE_CODE_55__(see below).
__INLINE_CODE_56__
- __INLINE_CODE_57__
Returns the __INLINE_CODE_58__ tag that is rendered in a target and clears the style sheet. Defaults to __INLINE_CODE_59__.
__CODE_BLOCK_20__
__INLINE_CODE_60__
To define your global styles you need to create a __INLINE_CODE_61__ component and use it as part of your tree. The __INLINE_CODE_62__ is available at __INLINE_CODE_63__ addon.
__CODE_BLOCK_21__
How about using __INLINE_CODE_64__ function directly?
Before the global addon, __INLINE_CODE_65__, there was a method named __INLINE_CODE_66__ that was part of the main package that would do the same thing, more or less. Having only that method to define global styles usually led to missing global styles from the extracted css, since the pattern did not enforce the evaluation of the styles at render time. The __INLINE_CODE_67__ method is still exported from __INLINE_CODE_68__, in case you have a hard dependency on it. It still has the same API:
__CODE_BLOCK_22__
__INLINE_CODE_69__
__INLINE_CODE_70__ is a helpful method to define reusable animations that can be decoupled from the main style declaration and shared across components.
__CODE_BLOCK_23__
__INLINE_CODE_71__
To implement the __INLINE_CODE_72__ without the need to provide the full loop over __INLINE_CODE_73__ you can use the __INLINE_CODE_74__ addon.
__CODE_BLOCK_24__
Integrations
Babel plugin
You're in love with the __INLINE_CODE_75__ syntax? Fear no more! We got you covered with a babel plugin that will take your lovely syntax from __INLINE_CODE_76__ and translate it to goober's __INLINE_CODE_77__ call.
__CODE_BLOCK_25__
Visit the package in here for more info (https://github.com/cristianbote/goober/tree/master/packages/babel-plugin-transform-goober)
Babel macro plugin
A babel-plugin-macros macro for [goober][goober], rewriting __INLINE_CODE_78__ syntax to __INLINE_CODE_79__ calls.
Usage
Once you've configured babel-plugin-macros, change your imports from __INLINE_CODE_80__ to __INLINE_CODE_81__.
Now you can create your components using __INLINE_CODE_82__ syntax:.
__CODE_BLOCK_26__
Next.js
Want to use __INLINE_CODE_83__ with Next.js? We've got you covered! Follow the example below or from the main examples directory.
__CODE_BLOCK_27__
Gatsby
Want to use __INLINE_CODE_84__ with Gatsby? We've got you covered! We have our own plugin to deal with styling your Gatsby projects.
__CODE_BLOCK_28__
Preact CLI plugin
If you use Goober with Preact CLI, you can use preact-cli-goober-ssr
__CODE_BLOCK_29__
When you build your Preact application, this will run __INLINE_CODE_85__ on your pre-rendered pages and add critical styles for each page.
CSS Prop
You can use a custom __INLINE_CODE_86__ prop to pass in styles on HTML elements with this Babel plugin.
Installation:
__CODE_BLOCK_30__
List the plugin in __INLINE_CODE_87__:
__CODE_BLOCK_31__
Usage:
__CODE_BLOCK_32__
Features
- Basic CSS parsing
- Nested rules with pseudo selectors
- Nested styled components
- Extending Styles
- Media queries (@media)
- Keyframes (@keyframes)
- Smart (lazy) client-side hydration
- Styling any component
- via __INLINE_CODE_88__
- Vanilla (via __INLINE_CODE_89__ function)
- __INLINE_CODE_90__ (via __INLINE_CODE_91__) so one would be able to create global styles
- target/extract from elements other than __INLINE_CODE_92__
- vendor prefixing
Content Security Policy (CSP)
goober supports Content Security Policy nonces for inline styles. Set __INLINE_CODE_93__ before loading the library:
__CODE_BLOCK_33__
The nonce will be added to goober's __INLINE_CODE_94__ element.
Sharing style
There are a couple of ways to effectively share/extend styles across components.
Extending
You can extend the desired component that needs to be enriched or overwritten with another set of css rules.
__CODE_BLOCK_34__
Using __INLINE_CODE_95__ prop
Another helpful way to extend a certain component is with the __INLINE_CODE_96__ property. Given our example above we could modify it like:
__CODE_BLOCK_35__
Autoprefixer
Autoprefixing is a helpful way to make sure the generated css will work seamlessly on the whole spectrum of browsers. With that in mind, the core __INLINE_CODE_97__ package can't hold that logic to determine the autoprefixing needs, so we added a new package that you can choose to address them.
__CODE_BLOCK_36__
After the main package is installed it's time to bootstrap goober with it:
__CODE_BLOCK_37__
And voilà! It is done!
TypeScript
__INLINE_CODE_98__ comes with type definitions build in, making it easy to get started in TypeScript straight away.
Prop Types
If you're using custom props and wish to style based on them, you can do so as follows:
__CODE_BLOCK_38__
Extending Theme
If you're using a custom theme and want to add types to it, you can create a declaration file at the base of your project.
__CODE_BLOCK_39__
You should now have autocompletion for your theme.
__CODE_BLOCK_40__
Browser support
__INLINE_CODE_99__ supports all major browsers (Chrome, Edge, Firefox, Safari).
To support IE 11 and older browsers, make sure to use a tool like Babel to transform your code into code that works in the browsers you target.
Contributing
Feel free to try it out and checkout the examples. If you wanna fix something feel free to open a issue or a PR.
Backers
Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website.

;
})
);
Integrations
Babel plugin
You're in love with the __INLINE_CODE_75__ syntax? Fear no more! We got you covered with a babel plugin that will take your lovely syntax from __INLINE_CODE_76__ and translate it to goober's __INLINE_CODE_77__ call.
__CODE_BLOCK_25__
Visit the package in here for more info (https://github.com/cristianbote/goober/tree/master/packages/babel-plugin-transform-goober)
Babel macro plugin
A babel-plugin-macros macro for [goober][goober], rewriting __INLINE_CODE_78__ syntax to __INLINE_CODE_79__ calls.
Usage
Once you've configured babel-plugin-macros, change your imports from __INLINE_CODE_80__ to __INLINE_CODE_81__.
Now you can create your components using __INLINE_CODE_82__ syntax:.
__CODE_BLOCK_26__
Next.js
Want to use __INLINE_CODE_83__ with Next.js? We've got you covered! Follow the example below or from the main examples directory.
__CODE_BLOCK_27__
Gatsby
Want to use __INLINE_CODE_84__ with Gatsby? We've got you covered! We have our own plugin to deal with styling your Gatsby projects.
__CODE_BLOCK_28__
Preact CLI plugin
If you use Goober with Preact CLI, you can use preact-cli-goober-ssr
__CODE_BLOCK_29__
When you build your Preact application, this will run __INLINE_CODE_85__ on your pre-rendered pages and add critical styles for each page.
CSS Prop
You can use a custom __INLINE_CODE_86__ prop to pass in styles on HTML elements with this Babel plugin.
Installation:
__CODE_BLOCK_30__
List the plugin in __INLINE_CODE_87__:
__CODE_BLOCK_31__
Usage:
__CODE_BLOCK_32__
Features
- Basic CSS parsing
- Nested rules with pseudo selectors
- Nested styled components
- Extending Styles
- Media queries (@media)
- Keyframes (@keyframes)
- Smart (lazy) client-side hydration
- Styling any component
- via __INLINE_CODE_88__
- Vanilla (via __INLINE_CODE_89__ function)
- __INLINE_CODE_90__ (via __INLINE_CODE_91__) so one would be able to create global styles
- target/extract from elements other than __INLINE_CODE_92__
- vendor prefixing
Content Security Policy (CSP)
goober supports Content Security Policy nonces for inline styles. Set __INLINE_CODE_93__ before loading the library:
__CODE_BLOCK_33__
The nonce will be added to goober's __INLINE_CODE_94__ element.
Sharing style
There are a couple of ways to effectively share/extend styles across components.
Extending
You can extend the desired component that needs to be enriched or overwritten with another set of css rules.
__CODE_BLOCK_34__
Using __INLINE_CODE_95__ prop
Another helpful way to extend a certain component is with the __INLINE_CODE_96__ property. Given our example above we could modify it like:
__CODE_BLOCK_35__
Autoprefixer
Autoprefixing is a helpful way to make sure the generated css will work seamlessly on the whole spectrum of browsers. With that in mind, the core __INLINE_CODE_97__ package can't hold that logic to determine the autoprefixing needs, so we added a new package that you can choose to address them.
__CODE_BLOCK_36__
After the main package is installed it's time to bootstrap goober with it:
__CODE_BLOCK_37__
And voilà! It is done!
TypeScript
__INLINE_CODE_98__ comes with type definitions build in, making it easy to get started in TypeScript straight away.
Prop Types
If you're using custom props and wish to style based on them, you can do so as follows:
__CODE_BLOCK_38__
Extending Theme
If you're using a custom theme and want to add types to it, you can create a declaration file at the base of your project.
__CODE_BLOCK_39__
You should now have autocompletion for your theme.
__CODE_BLOCK_40__
Browser support
__INLINE_CODE_99__ supports all major browsers (Chrome, Edge, Firefox, Safari).
To support IE 11 and older browsers, make sure to use a tool like Babel to transform your code into code that works in the browsers you target.
Contributing
Feel free to try it out and checkout the examples. If you wanna fix something feel free to open a issue or a PR.
Backers
Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website.

) {
delete props[prop];
}
}
});
Alternatively you can use __INLINE_CODE_36__ addon to pass only the filter function and not have to deal with the full __INLINE_CODE_37__ object.
__CODE_BLOCK_14____INLINE_CODE_38__
- __INLINE_CODE_39__ Returns the className.
To create a className, you need to call __INLINE_CODE_40__ with your style rules in a tagged template.
__CODE_BLOCK_15__Different ways of customizing __INLINE_CODE_41__
Passing props to __INLINE_CODE_42__ tagged templates
__CODE_BLOCK_16__Using __INLINE_CODE_43__ with JSON/Object
__CODE_BLOCK_17__Notice: using __INLINE_CODE_44__ with object can reduce your bundle size.
We can also declare styles at the top of the file by wrapping __INLINE_CODE_45__ into a function that we call to get the className.
__CODE_BLOCK_18__The difference between calling __INLINE_CODE_46__ directly and wrapping into a function is the timing of its execution. The former is when the component(file) is imported, the latter is when it is actually rendered.
If you use __INLINE_CODE_47__ for SSR, you may prefer to use the latter, or the __INLINE_CODE_48__ API to avoid inconsistent results.
__INLINE_CODE_49__
By default, goober will append a style tag to the __INLINE_CODE_50__ of a document. You might want to target a different node, for instance, when you want to use goober with web components (so you'd want it to append style tags to individual shadowRoots). For this purpose, you can __INLINE_CODE_51__ a new target to the __INLINE_CODE_52__ and __INLINE_CODE_53__ methods:
__CODE_BLOCK_19__If you don't provide a target, goober always defaults to __INLINE_CODE_54__ and in environments without a DOM (think certain SSR solutions), it will just use a plain string cache to store generated styles which you can extract with __INLINE_CODE_55__(see below).
__INLINE_CODE_56__
- __INLINE_CODE_57__
Returns the __INLINE_CODE_58__ tag that is rendered in a target and clears the style sheet. Defaults to __INLINE_CODE_59__.
__CODE_BLOCK_20____INLINE_CODE_60__
To define your global styles you need to create a __INLINE_CODE_61__ component and use it as part of your tree. The __INLINE_CODE_62__ is available at __INLINE_CODE_63__ addon.
__CODE_BLOCK_21__How about using __INLINE_CODE_64__ function directly?
Before the global addon, __INLINE_CODE_65__, there was a method named __INLINE_CODE_66__ that was part of the main package that would do the same thing, more or less. Having only that method to define global styles usually led to missing global styles from the extracted css, since the pattern did not enforce the evaluation of the styles at render time. The __INLINE_CODE_67__ method is still exported from __INLINE_CODE_68__, in case you have a hard dependency on it. It still has the same API:
__CODE_BLOCK_22____INLINE_CODE_69__
__INLINE_CODE_70__ is a helpful method to define reusable animations that can be decoupled from the main style declaration and shared across components.
__CODE_BLOCK_23____INLINE_CODE_71__
To implement the __INLINE_CODE_72__ without the need to provide the full loop over __INLINE_CODE_73__ you can use the __INLINE_CODE_74__ addon.
__CODE_BLOCK_24__Integrations
Babel plugin
You're in love with the __INLINE_CODE_75__ syntax? Fear no more! We got you covered with a babel plugin that will take your lovely syntax from __INLINE_CODE_76__ and translate it to goober's __INLINE_CODE_77__ call.
__CODE_BLOCK_25__Visit the package in here for more info (https://github.com/cristianbote/goober/tree/master/packages/babel-plugin-transform-goober)
Babel macro plugin
A babel-plugin-macros macro for [goober][goober], rewriting __INLINE_CODE_78__ syntax to __INLINE_CODE_79__ calls.
Usage
Once you've configured babel-plugin-macros, change your imports from __INLINE_CODE_80__ to __INLINE_CODE_81__.
Now you can create your components using __INLINE_CODE_82__ syntax:.
__CODE_BLOCK_26__Next.js
Want to use __INLINE_CODE_83__ with Next.js? We've got you covered! Follow the example below or from the main examples directory.
__CODE_BLOCK_27__Gatsby
Want to use __INLINE_CODE_84__ with Gatsby? We've got you covered! We have our own plugin to deal with styling your Gatsby projects.
__CODE_BLOCK_28__Preact CLI plugin
If you use Goober with Preact CLI, you can use preact-cli-goober-ssr
__CODE_BLOCK_29__When you build your Preact application, this will run __INLINE_CODE_85__ on your pre-rendered pages and add critical styles for each page.
CSS Prop
You can use a custom __INLINE_CODE_86__ prop to pass in styles on HTML elements with this Babel plugin.
Installation:
__CODE_BLOCK_30__List the plugin in __INLINE_CODE_87__:
__CODE_BLOCK_31__Usage:
__CODE_BLOCK_32__Features
- Basic CSS parsing
- Nested rules with pseudo selectors
- Nested styled components
- Extending Styles
- Media queries (@media)
- Keyframes (@keyframes)
- Smart (lazy) client-side hydration
- Styling any component
- via __INLINE_CODE_88__
- Vanilla (via __INLINE_CODE_89__ function)
- __INLINE_CODE_90__ (via __INLINE_CODE_91__) so one would be able to create global styles
- target/extract from elements other than __INLINE_CODE_92__
- vendor prefixing
Content Security Policy (CSP)
goober supports Content Security Policy nonces for inline styles. Set __INLINE_CODE_93__ before loading the library:
__CODE_BLOCK_33__The nonce will be added to goober's __INLINE_CODE_94__ element.
Sharing style
There are a couple of ways to effectively share/extend styles across components.
Extending
You can extend the desired component that needs to be enriched or overwritten with another set of css rules.
__CODE_BLOCK_34__Using __INLINE_CODE_95__ prop
Another helpful way to extend a certain component is with the __INLINE_CODE_96__ property. Given our example above we could modify it like:
__CODE_BLOCK_35__Autoprefixer
Autoprefixing is a helpful way to make sure the generated css will work seamlessly on the whole spectrum of browsers. With that in mind, the core __INLINE_CODE_97__ package can't hold that logic to determine the autoprefixing needs, so we added a new package that you can choose to address them.
__CODE_BLOCK_36__After the main package is installed it's time to bootstrap goober with it:
__CODE_BLOCK_37__And voilà! It is done!
TypeScript
__INLINE_CODE_98__ comes with type definitions build in, making it easy to get started in TypeScript straight away.
Prop Types
If you're using custom props and wish to style based on them, you can do so as follows:
__CODE_BLOCK_38__Extending Theme
If you're using a custom theme and want to add types to it, you can create a declaration file at the base of your project.
__CODE_BLOCK_39__You should now have autocompletion for your theme.
__CODE_BLOCK_40__Browser support
__INLINE_CODE_99__ supports all major browsers (Chrome, Edge, Firefox, Safari).
To support IE 11 and older browsers, make sure to use a tool like Babel to transform your code into code that works in the browsers you target.
Contributing
Feel free to try it out and checkout the examples. If you wanna fix something feel free to open a issue or a PR.
Backers
Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website.