Skip to content

miguelc1221/glamorous

Β 
Β 

Repository files navigation

glamorous logo

glamorous

React component styling solved with an elegant (inspired) API, small footprint, and great performance (via glamor).

Read the intro blogpost

Build Status Code Coverage Dependencies version downloads MIT License

All Contributors PRs Welcome Chat Donate Code of Conduct Roadmap Examples

gzip size size module formats: umd, cjs, and es Watch on GitHub Star on GitHub Tweet

The problem

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}
    />
  )
}

This solution

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!

Installation

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 module
  • global: dist/glamorous.umd.js and dist/glamorous.umd.min.js - exports itself as a umd module which is consumable in several environments, the most notable as a global.
  • jsnext:main and module: 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'

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>

Video Intro πŸ“Ί

Video Screenshot

Terms and concepts

glamorous

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.

built-in DOM component factories

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}

glamorousComponentFactory

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}
...styles

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}

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 MyStyledDiv = glamorous.div(
  className1,
  styles2,
  className3,
  styles4,
  'extra-thing',
)
<MyStyledDiv /> // styles applied: {padding-top: 1, padding-right: 2, padding-bottom: 3, padding-left: 4} and anything coming from `extra-thing`.

GlamorousComponent

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.

supported props

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.

innerRef

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 () {
    <MyDiv innerRef={c => this._divRef = c} />
  }
}
other props

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 GlamorousComponents (see below).

built-in GlamorousComponents

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 make 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>

Overriding component styles

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 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})
using className

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
using css

This is an object and if provided, it will be merged with this component's 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
using glamorous() composition

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.

glamorous API

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:

displayName

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'}
)

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.

rootEl

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(big => ({
  fontSize: 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

forwardProps

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',
})(big => ({
  fontSize: 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`.

Theming

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 a ThemeProvider further up the render tree and will warn in development if one is not found!

Size

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:

  1. No built-in component factories (glamorous.article({/* styles */})) So you have to create your own (glamorous('article')({/* styles */}))
  2. No built-in glamorous components (glamorous.Span)
  3. No props filtering for dynamic styles, instead, you place them on a special glam prop (see the example below).
  4. If you need ThemeProvider or withTheme, you must import those manually. They are not exported as part of glamorous/tiny like they are with glamorous.

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:

  1. glamorous/dist/glamorous.es.tiny.js - use if you're using Webpack@>=2 or Rollup
  2. glamorous/dist/glamorous.cjs.tiny - use if you're not transpiling ESModules
  3. glamorous/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: tiny size tiny gzip size

IMPORTANT NOTE ABOUT SIZE: Because glamorous depends on glamor, you should consider the full size you'll be adding to your application if you don't already have glamor. The current size of glamor/umd/index.min.js is: glamor size glamor gzip size

Server Side Rendering (SSR)

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.

Example Style Objects

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>

Users

Who uses glamorous? See other/USERS.md and add yourself if you use glamorous!

Inspiration

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:

  1. Not to ship a CSS parser to the browser (because it's huge and less performant).
  2. Support for RTL (via something like rtl-css-js)
  3. 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.

Other Solutions

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.

Support

If you need help, please fork this CodeSandbox and bring it up in the chat

Contributors

Thanks goes to these people (emoji key):


Kent C. Dodds

πŸ’» πŸ“– πŸš‡ ⚠️ πŸ‘€

Ives van Hoorne

πŸ’‘

Gerardo Nardelli

πŸ“–

Chandan Rai

πŸ“–

BinHong Lee

πŸ“–

Paul Molluzzo

πŸ“– πŸ’‘

Sriram Thiagarajan

πŸ’»

Pavithra Kodmad

πŸ’‘

Alessandro Arnodo

πŸ’» πŸ“– ⚠️

Jason Miller

πŸ‘€

Kyle Welch

πŸ‘€ πŸ’‘

Javi Velasco

πŸ‘€

Brandon Dail

πŸ‘€

Jason Brown

πŸ‘€

jackyho112

πŸ’» ⚠️

Kurtis Kemple

πŸ’» ⚠️ πŸ“–

Bernardo Raposo

πŸ“– πŸ’‘

Ryan Delaney

πŸ’»

Anthony Ng

πŸ“–

Matthew Crutchfield

πŸ’‘

Kye Hohenberger

πŸ’» ⚠️ πŸ“–

Bernard Lin

πŸ“– πŸ”Œ

Miguel Correa

πŸ’»

Brian Hough

πŸ’‘

Erik Cupal

πŸ’»

Kok J Sam

πŸ’»

Oleg Proskurin

πŸ“–

Luke John

πŸ’»

This project follows the all-contributors specification. Contributions of any kind welcome!

LICENSE

MIT

About

React component styling solved πŸ’„

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • JavaScript 95.1%
  • TypeScript 4.9%