React component styling solved with an elegant (inspired) API,
small footprint, and great performance (via glamor
).
Read the intro blogpost
Table of Contents
- The problem
- This solution
- Installation
- Video Intro πΊ
- Terms and concepts
- Related projects
- Users
- Inspiration
- Other Solutions
- Support
- Swag π
- Contributors
- LICENSE
You like CSS in JS, but you don't like having to create entire component
functions just for styling purposes. You don't want to give a name to something
that's purely style-related. And it's just kind of annoying to do the
style-creating, className
assigning, and props-forwarding song and dance.
For example, this is what you have to do with raw glamor
(or aphrodite
or
similar for that matter):
const styles = glamor.css({
fontSize: 20,
textAlign: 'center',
})
function MyStyledDiv({className = '', ...rest}) {
return (
<div
className={`${styles} ${className}`}
{...rest}
/>
)
}
With glamorous
, that example above looks as simple as this:
const MyStyledDiv = glamorous.div({
fontSize: 20,
textAlign: 'center',
})
In fact, it's even better, because there are a bunch of features that make composing these components together really nice!
Oh, and what if you didn't care to give MyStyledDiv
a name? If you just want
a div that's styled using glamor? You can do that too:
const { Div } = glamorous
function App() {
return (
<Div
fontSize={20}
textAlign="center"
>
Hello world!
</Div>
)
}
Try this out in your browser here!
So that's the basics of this solution... Let's get to the details!
This module is distributed via npm which is bundled with node and
should be installed as one of your project's dependencies
:
npm install --save glamorous
This also depends on react
and glamor
so you'll need those in your project
as well (if you don't already have them):
npm install --save react glamor
NOTE: If you're using React v15.5 or greater, you'll also need to have
prop-types
installed:npm install --save prop-types
You can then use one of the module formats:
main
:dist/glamorous.cjs.js
- exports itself as a CommonJS moduleglobal
:dist/glamorous.umd.js
anddist/glamorous.umd.min.js
- exports itself as a umd module which is consumable in several environments, the most notable as a global.jsnext:main
andmodule
:dist/glamorous.es.js
- exports itself using the ES modules specification, you'll need to configure webpack to make use of this file do this using the resolve.mainFields property.
The most common use-case is consuming this module via CommonJS:
const glamorous = require('glamorous')
const {ThemeProvider} = glamorous
// etc.
If you're transpiling (and/or using the jsnext:main
):
import glamorous, {ThemeProvider} from 'glamorous'
// you can also import specific Glamorous Components (see section below on "Built-in" components)
import {Div, H2} from 'glamorous'
// tags with the same name as built-in JavaScript objects are importable with a Tag suffix
// and tag names that contain dashes are transformed to CamelCase
import {MapTag, ColorProfile} from 'glamorous'
If you want to use the global:
<!-- Load dependencies -->
<script src="https://unpkg.com/react/dist/react.js"></script>
<script src="https://unpkg.com/prop-types/prop-types.js"></script>
<script src="https://unpkg.com/glamor/umd/index.js"></script>
<!-- load library -->
<script src="https://unpkg.com/glamorous/dist/glamorous.umd.js"></script>
<script>
// Use window.glamorous here...
const glamorous = window.glamorous
const {ThemeProvider} = glamorous
</script>
glamorous
offers a version for React Native projects called glamorous-native
.
npm install glamorous-native --save
You can learn more at the glamorous-native project.
glamorous
includes typescript definition files.
For usage notes and known issues see other/TYPESCRIPT_USAGE.md.
The glamorous
function is the main (only) export. It allows you to create
glamorous components that render the styles to the component you give it. This
is done by forwarding a className
prop to the component you tell it to render.
But before we get into how you wrap custom components, let's talk about the
built-in DOM components.
For every DOM element, there is an associated glamorous
component factory
attached to the glamorous
function. As above, you can access these factories
like so: glamorous.div
, glamorous.a
, glamorous.article
, etc.
const MyStyledSection = glamorous.section({ margin: 1 })
<MyStyledSection>content</MyStyledSection>
// rendered output: <section class="<glamor-generated-class>">content</section>
// styles applied: {margin: 1}
Whether you create one yourself or use one of the built-in ones mentioned above,
each glamorousComponentFactory
allows you to invoke it with styles and it
returns you a new component which will have those styles applied when it's
rendered. This is accomplished by generating a className
for the styles you
give and forwarding that className
onto the rendered element. So if you're
wrapping a component you intend to style, you'll need to make sure you accept
the className
as a prop and apply it to where you want the styles applied in
your custom component (normally the root element).
const UnstyledComp = ({ className, children }) => <div className={`${className} other-class`}>{children}</div>
const MyStyledComp = glamorous(UnstyledComp)({ margin: 1 })
<MyStyledComp>content</MyStyledComp>
// rendered output: <div class="<glamor-generated-class> other-class">content</div>
// styles applied: {margin: 1}
The glamorousComponentFactory
accepts any number of style object arguments.
These can be style objects or functions which are invoked with props
on every
render and return style objects. To learn more about what these style objects
can look like, please take a look at the glamor
documentation.
const MyStyledDiv = glamorous.div(
{
margin: 1,
},
(props) => ({
padding: props.noPadding ? 0 : 4,
})
)
<MyStyledDiv /> // styles applied: {margin: 1, padding: 4}
<MyStyledDiv noPadding /> // styles applied: {margin: 1, padding: 0}
Tip: glamorous simply takes these style objects and forwards them to
glamor
.glamor
will then merge those together in a way you would expect. One neat thing you can do is specify an array of style objects andglamor
will treat that exactly the same. It's really expressive! See the examples for an example of how this works.
You can also specify other classes you'd like applied to the component as well. If these classes are generated by glamor, then their styles will be merged with the glamor style's, otherwise the class name will simply be forwarded. For example:
const className1 = glamor.css({paddingTop: 1, paddingRight: 1}).toString()
const styles2 = {paddingRight: 2, paddingBottom: 2}
const className3 = glamor.css({paddingBottom: 3, paddingLeft: 3}).toString()
const styles4 = {paddingLeft: 4}
const styles5 = props => (props.active ? 'active' : 'not-active')
const MyStyledDiv = glamorous.div(
className1,
styles2,
className3,
styles4,
styles5,
'extra-thing',
)
<MyStyledDiv /> // styles applied: {padding-top: 1, padding-right: 2, padding-bottom: 3, padding-left: 4}, 'not-active' and anything coming from `extra-thing`.
The GlamorousComponent
is what is returned from the glamorousComponentFactory
.
Its job is to get all the styles together, get a className
(from glamor
)
and forward that on to your component.
By default GlamorousComponent
supports three props: className
, css
and theme
which are used to override the styles of the component in different scenarios. For a
more detailed explanation see Overriding component styles and Theming sections below.
This is a function and if provided, will be called with the inner element's reference.
const MyDiv = glamorous.div({ padding: 20 })
// You can get a reference to the inner element with the `innerRef` prop
class MyComponent extends React.Component {
render () {
return (
<MyDiv innerRef={c => this._divRef = c} />
)
}
}
Only props that are safe to forward to the specific element
(ie. that will
ultimately be rendered) will be forwarded. So this is totally legit:
<MyStyledDiv size="big" />
A use case for doing something like this would be for dynamic styles:
const staticStyles = {color: 'green'}
const dynamicStyles = props => ({fontSize: props.size === 'big' ? 32 : 24})
const MyDynamicallyStyledDiv = glamorous.div(staticStyles, dynamicStyles)
The exception to this prop forwarding is the pre-created
GlamorousComponent
s (see below).
Often you want to style something without actually giving it a name (because
naming things is hard). So glamorous also exposes a pre-created
GlamorousComponent
for each DOM node type which makes this reasonable to do:
const { Div, Span, A, Img } = glamorous
function MyUserInterface({name, tagline, imageUrl, homepage, size}) {
const nameSize = size
const taglineSize = size * 0.5
return (
<Div display="flex" flexDirection="column" justifyContent="center">
<A href={homepage} textDecoration="underline" color="#336479">
<Img borderRadius="50%" height={180} src={imageUrl} />
<Div fontSize={nameSize} fontWeight="bold">{name}</Div>
</A>
<Span fontSize={taglineSize} color="#767676">
{tagline}
</Span>
</Div>
)
}
Try this out in your browser here!
Having to name all of that stuff could be tedious, so having these pre-built
components is handy. The other handy bit here is that the props are the styles
for these components. Notice that glamorous can distinguish between props that
are for styling and those that are have semantic meaning (like with the Img
and A
components which make use of src
and href
props).
The css
prop can be used to provide styles as an object.
import glamorous, {withTheme} from 'glamorous'
const { Div, Span } = glamorous
const predefinedStyle = {
color: '#767676',
fontSize: 18,
}
const MyUserInterface = withTheme(function ({tagline, theme}) {
return (
<Div
css={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
[theme.mq.tablet]: {
flexDirection: 'row'
}
}}
>
<Span css={predefinedStyle}>
{tagline}
</Span>
</Div>
)
})
One other tip... This totally works:
<glamorous.Div color="blue">
JSX is pretty wild!
</glamorous.Div>
The most common scenario for using props is to override the style of an
existing component (generated by glamorous
or not). That can be achieved
by using the props className
, css
and theme
or simply component
composition with the glamorous()
function.
If you're interested in knowing more about using the theme
prop, see the
Theming section instead for a more detailed explanation. In this
section we'll explain how to use className
, css
and composition to override
the styles of a component.
Let's see how that can be done in the examples below.
Try this out in your browser here!
We'll use this as our GlamorousComponent
:
const MyStyledDiv = glamorous.div({margin: 1, fontSize: 1, padding: 1})
For each className
you provide, the GlamorousComponent
will check to see
whether it is a glamor
generated className
(can be from raw glamor
or from glamorous
, doesn't matter). If it is, it will get the original styles
that were used to generate that className
and merge those with the styles for
the element that's rendered in a way that the provided className
's styles win
in the event of a conflict.
If the className
is not generated by glamor
, then it will simply be
forwarded along with the GlamorousComponent
-generated className
.
const myCustomGlamorStyles = glamor.css({fontSize: 2})
<MyStyledDiv className={`${myCustomGlamorStyles} custom-class`} />
// styles applied:
// {margin: 1, fontSize: 2, padding: 1}
// as well as any styles custom-class applies
This can be the same type as any of the styles provided
(as in glamorous.div(...styles)
). If specified, it will be merged with this
component's styles and take highest priority over the component's predefined
styles.
const myCustomGlamorStyles = glamor.css({fontSize: 2, padding: 2})
<MyStyledDiv
className={`${myCustomGlamorStyles} custom-class`}
css={{padding: 3}}
/>
// styles applied:
// {margin: 1, fontSize: 2, padding: 3}
// as well as any styles custom-class applies
If we just want to extend the styles of an existing component it can be done
by using the glamorous()
function.
const MyComposedStyledDiv = glamorous(MyStyledDiv)({fontSize: 4, padding: 4})
<MyComposedStyledDiv />
// styles applied:
// {margin: 1, fontSize: 4, padding: 4}
In fact, the built-in DOM component factories provided are just an abstraction
of this function, so glamorous.div
could be written as glamorous('div')
instead.
The glamorous
function allows you to create your own
glamorousComponentFactory
(see above) for any
component you have. For example:
const MyComponent = props => <div {...props} />
const myGlamorousComponentFactory = glamorous(MyComponent)
const MyGlamorousComponent = myGlamorousComponentFactory({/* styles */})
<MyGlamorousComponent id="i-am-forwarded-to-the-div" />
You can also provide a few options to help glamorous know how to handle your component:
The displayName
of a React component is used by React in the
React DevTools and is really handy for debugging React
applications. Glamorous will do its best to give a good displayName
for your
component, but, for the example above, the best it can do is:
glamorous(MyComponent)
. If you want to specify a displayName
, you can do
so with this property. For example:
const MyComponent = props => <div {...props} />
const myGlamorousComponentFactory = glamorous(
MyComponent,
{displayName: 'MyGlamorousComponent'}
)
Note: the
displayName
can also included in the className that your components are given which makes the development experience a bit nicer. To enable this see the section aboutconfig
. This will likely be enabled by default in the next major change.
And now all components created by the myGlamorousComponentFactory
will have
the displayName
of MyGlamorousComponent
.
There is also a babel plugin that can monkey-patch the displayName
onto
the components that you create from your component factory.
React has an Unknown Prop Warning that it logs when you
pass spurious props to DOM elements: (i.e. <div big={true} />
). Because you
can style your components using props, glamorous needs to filter out the props
you pass so it doesn't forward these on to the underlying DOM element. However,
if you create your own factory using a custom component, glamorous will just
forward all the props (because the component may actually need them and
glamorous has no way of knowing). But in some cases, the component may be
spreading all of the props onto the root element that it renders. For these
cases, you can tell glamorous which element is being rendered and glamorous will
apply the same logic for which props to forward that it does for the built-in
factories. For example:
const MyComponent = props => <div {...props} />
const myGlamorousComponentFactory = glamorous(
MyComponent,
{rootEl: 'div'}
)
const MyGlamorousComponent = myGlamorousComponentFactory(props => ({
fontSize: props.big ? 36 : 24,
}))
<MyGlamorousComponent big={true} id="room423" />
// this will render:
// <div id="room423" />
// with {fontSize: 36}
// `big` is not forwarded to MyComponent because the `rootEl` is a `div` and `big`
// is not a valid attribute for a `div` however `id` will be forwarded because
// `id` is a valid prop
There are some cases where you're making a glamorousComponentFactory
out of
a custom component that spreads some of the properties across an underlying
DOM element, but not all of them. In this case you should use rootEl
to
forward only the right props to be spread on the DOM element, but you can also
use forwardProps
to specify extra props that should be forwarded. For
example:
const MyComponent = ({shouldRender, ...rest}) => (
shouldRender ? <div {...rest} /> : null
)
const MyStyledComponent = glamorous(MyComponent, {
forwardProps: ['shouldRender'],
rootEl: 'div',
})(props => ({
fontSize: props.big ? 36 : 24,
}))
<MyStyledComponent shouldRender={true} big={false} id="hello" />
// this will render:
// <div id="hello" />
// with {fontSize: 24}
// `shouldRender` will be forwarded to `MyComponent` because it is included in
// `forwardProps`. `big` will not be forwarded to `MyComponent` because `rootEl`
// is a `div` and that's not a valid prop for a `div`, but it will be used in
// the styles object function that determines the `fontSize`. Finally `id` will
// be forwarded to `MyComponent` because it is a valid prop for a `div`.
In some cases you might want to just copy the styles of an already created
glamorous component with a different tag altogether, withComponent
function
comes in handy then. For example:
const Button = glamorous.button({
display: 'inline-block',
color: 'red',
fontSize: '16px',
margin: '16px',
padding: '8px 16px',
border: '1px solid red',
borderRadius: '4px',
});
// We're replacing the <button> tag with an <a> tag, but reuse all the same styles
const Link = Button.withComponent('a')
<Button>Normal Button</Button>
<Link>Normal Link</Link>
// this will render:
// <button>Normal Button</button>
// <a>Normal Link</a>
// both with the same styles
Note: to override styles, you can do the same thing you do with a regular component (
css
prop, wrap it inglamorous()
, or regularclassName
prop).
glamorous
fully supports theming using a special <ThemeProvider>
component.
It provides the theme
to all glamorous components down the tree.
Try this out in your browser here!
import glamorous, {ThemeProvider} from 'glamorous'
// our main theme object
const theme = {
main: {color: 'red'}
}
// our secondary theme object
const secondaryTheme = {
main: {color: 'blue'}
}
// a themed <Title> component
const Title = glamorous.h1({
fontSize: '10px'
}, (props, theme) => ({
color: theme.main.color
}))
// use <ThemeProvider> to pass theme down the tree
<ThemeProvider theme={theme}>
<Title>Hello!</Title>
</ThemeProvider>
// it is possible to nest themes
// inner themes will be merged with outers
<ThemeProvider theme={theme}>
<div>
<Title>Hello!</Title>
<ThemeProvider theme={secondaryTheme}>
{/* this will be blue */}
<Title>Hello from here!</Title>
</ThemeProvider>
</div>
</ThemeProvider>
// to override a theme, just pass a theme prop to a glamorous component
// the component will ignore any surrounding theme, applying the one passed directly via props
<ThemeProvider theme={theme}>
{/* this will be yellow */}
<Title theme={{main: {color: 'yellow'}}}>Hello!</Title>
</ThemeProvider>
glamorous
also exports a withTheme
higher order component (HOC) so you can access your theme in any component!
Try this out in your browser here!
import glamorous, {ThemeProvider, withTheme} from 'glamorous'
// our main theme object
const theme = {
main: {color: 'red'}
}
// a themed <Title> component
const Title = glamorous.h1({
fontSize: '10px'
}, (props, theme) => ({
color: theme.main.color
}))
// normal component that takes a theme prop
const SubTitle = ({children, theme: {color}}) => (
<h3 style={{color}}>{children}</h3>
)
// extended component with theme prop
const ThemedSubTitle = withTheme(SubTitle)
<ThemeProvider theme={theme}>
<Title>Hello!</Title>
<ThemedSubTitle>from withTheme!</ThemedSubTitle>
</ThemeProvider>
Or if you prefer decorator syntax:
import React, {Component} from 'react'
import glamorous, {ThemeProvider, withTheme} from 'glamorous'
// our main theme object
const theme = {
main: {color: 'red'}
}
// a themed <Title> component
const Title = glamorous.h1({
fontSize: '10px'
}, (props, theme) => ({
color: theme.main.color
}))
// extended component with theme prop
@withTheme
class SubTitle extends Component {
render() {
const {children, theme: {main: {color}}} = this.props
return <h3 style={{color}}>{children}</h3>
}
}
<ThemeProvider theme={theme}>
<Title>Hello!</Title>
<SubTitle>from withTheme!</SubTitle>
</ThemeProvider>
withTheme
expects aThemeProvider
further up the render tree and will warn indevelopment
if one is not found!
You can configure glamorous globally with the config
object which you can
access via glamorous.config
.
Defaults to false
(will default to true
in a future major release).
This should only be used for debugging purposes. It is strongly discouraged
from referencing this className in your CSS due to the level of indirection that
will add (making it easier for you to break something when refactoring in the
future).
Example:
import glamorous from 'glamorous'
glamorous.config.useDisplayNameInClassName = true
const MyComponent = props => <div {...props} />
const myGlamorousComponentFactory = glamorous(
MyComponent,
{displayName: 'MyGlamorousComponent'}
)
const MyGlamorousComponent = myGlamorousComponentFactory()
<MyGlamorousComponent />
// renders <div class="css-nil MyGlamorousComponent" />
If you don't want to provide the displayName
for all of your components, then
you can use this babel-plugin. Otherwise, the className
will be simply: glamorous(MyComponent)
context is an unstable API and it's not recommended to use it directly. However, if you need to use it for some reason, here's an example of how you could do that:
const dynamicStyles = (props, theme, context) => ({
color: context.isLoggedIn ? 'green' : 'red'
})
const MyDiv = glamorous.div(dynamicStyles)
MyDiv.contextTypes = {
isLoggedIn: PropTypes.string,
}
class Parent extends React.Component {
getChildContext() {
return {
isLoggedIn: true,
}
}
render() {
return <MyDiv />
}
}
Parent.childContextTypes = {
isLoggedIn: PropTypes.string,
}
<Parent />
// renders <div />
// with {color: 'green'}
If your use case is really size constrained, then you might consider using the "tiny" version of glamorous for your application.
It's is a miniature version of glamorous
with a few limitations:
- No built-in component factories (
glamorous.article({/* styles */})
) So you have to create your own (glamorous('article')({/* styles */})
) - No built-in glamorous components (
glamorous.Span
) - No props filtering for dynamic styles, instead, you place them on a special
glam
prop (see the example below). - If you need
ThemeProvider
orwithTheme
, you must import those manually. They are not exported as part ofglamorous/tiny
like they are withglamorous
.
Here's an example of what you're able to do with it.
import React from 'react'
import glamorous from 'glamorous/dist/glamorous.es.tiny'
const Comp = glamorous('div')({
color: 'red'
}, (props) => ({
fontSize: props.glam.big ? 20 : 12
}))
function Root() {
return (
<Comp
glam={{big: true}}
thisWillBeForwardedAndReactWillWarn
>
ciao
</Comp>
)
}
export default Root
It's recommended to use either babel-plugin-module-resolver
or the resolve.alias
config with webpack so you don't have
to import from that full path. You have the following options available for this
import:
glamorous/dist/glamorous.es.tiny.js
- use if you're using Webpack@>=2 or Rollupglamorous/dist/glamorous.cjs.tiny
- use if you're not transpiling ESModulesglamorous/dist/glamorous.umd.tiny.js
- use if you're including it as a script tag. (There's also a.min.js
version).
The current size of glamorous/dist/glamorous.umd.tiny.min.js
is:
IMPORTANT NOTE ABOUT SIZE: Because
glamorous
depends onglamor
, you should consider the full size you'll be adding to your application if you don't already haveglamor
. The current size ofglamor/umd/index.min.js
is:
Because both glamor
and react
support SSR, glamorous
does too! I actually
do this on my personal site
which is generated at build-time on the server. Learn about rendering
react
on the server and glamor
too.
Style objects can affect pseudo-classes and pseudo-elements, complex CSS selectors, introduce keyframe animations, and use media queries:
pseudo-class
const MyLink = glamorous.a({
':hover': {
color: 'red'
}
})
// Use in a render function
<MyLink href="https://github.com">GitHub</MyLink>
pseudo-element
const MyListItem = glamorous.li({
listStyleType: 'none',
position: 'relative',
'&::before': {
content: `'#'`, // be sure the quotes are included in the passed string
display: 'block',
position: 'absolute',
left: '-20px',
width: '20px',
height: '20px'
}
})
// Use in a render function
<ul>
<MyListItem>Item 1</MyListItem>
<MyListItem>Item 2</MyListItem>
<MyListItem>Item 3</MyListItem>
</ul>
Relational CSS Selectors
const MyDiv = glamorous.div({
display: 'block',
'& div': { color: 'red' }, // child selector
'& div:first-of-type': { textDecoration: 'underline' }, // psuedo-selector
'& > p': { color: 'blue' } // direct descendent
})
// Use in a render function
<MyDiv>
<div><p>Red Underlined Paragraph</p></div>
<div>Red Paragraph</div>
<p>Blue Paragraph</p>
</MyDiv>
Animations
Try this in your browser
// import css from glamor
import { css } from 'glamor'
// Define the animation styles
const animationStyles = props => {
const bounce = css.keyframes({
'0%': { transform: `scale(1.01)` },
'100%': { transform: `scale(0.99)` }
})
return {animation: `${bounce} 0.2s infinite ease-in-out alternate`}
}
// Define the element
const AnimatedDiv = glamorous.div(animationStyles)
// Use in a render function
<AnimatedDiv>
Bounce.
</AnimatedDiv>
Media Queries
const MyResponsiveDiv = glamorous.div({
width: '100%',
padding: 20,
'@media(min-width: 400px)': {
width: '85%',
padding: 0
}
})
// Use in a render function
<MyResponsiveDiv>
Responsive Content
</MyResponsiveDiv>
- babel-plugin-glamorous-displayname: Automatically adds a
displayName
to your glamorous components for a better debugging experience.
Who uses glamorous
? See other/USERS.md and add yourself if you use glamorous
!
This package was inspired by the work from people's work on the following projects:
The biggest inspiration for building this is because I love the API offered by
styled-components
, but I wanted:
- Not to ship a CSS parser to the browser (because it's huge and less performant).
- Support for RTL (via something like rtl-css-js)
- Support for using real JavaScript objects rather than a CSS string (better tooling support, ESLint, etc.)
You can get around the parser issue if you use a certain babel plugin, but then you can't do any dynamic construction of your CSS string (like this) which is a bummer for custom utilities.
There are actually quite a few solutions to the general problem of styling in React. This isn't the place for a full-on comparison of features, but I'm unaware of any which supports all of the features which this library supports.
If you need help, please fork this CodeSandbox and bring it up in the chat
A community member created this awesome t-shirt, and they're now available on Amazon! For every shirt purchased, GSM Studio will donate $1 to Girls Who Code to support the next generation of programmers. The shirts come in various colors and sizes. In addition you can check out other clever t-shirts by GSM Studio.
Thanks goes to these people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!
MIT