glamorous API

glamorous API

่ฟ™ไปฝๆ–‡ๆกฃ่ฟ˜ๆฒกๆœ‰็ฟป่ฏ‘ใ€‚ไฝ ่ƒฝๅธฎๆˆ‘ไปฌ็ฟป่ฏ‘ๅ—๏ผŸ

The glamorous function allows you to create your own glamorousComponentFactory (see below) for any component you have.

const MyComponent = props => <div {...props} />
const myGlamorousComponentFactory = glamorous(MyComponent)
const MyGlamorousComponent = myGlamorousComponentFactory({/* styles */})

<MyGlamorousComponent id="i-am-forwarded-to-the-div" />

Try this out in your browser here!

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.

const MyComponent = props => <div {...props} />
const myGlamorousComponentFactory = glamorous(
  MyComponent,
  {displayName: 'MyGlamorousComponent'}
)

Try this out in your browser here!

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 about config. 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.

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.

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

Try this out in your browser here!

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.

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`.

Try this out in your browser here!

filterProps

Sometimes it's not possible to know which DOM element will be rendered at the end and therefore no rootEl can be set to avoid Unknown Prop Warnings. To filter out these properties from being forwarded to the child components you can set filterProps. filterProps takes an arrays of prop names.

const withFlex = Component => {
  return glamorous(Component, {
    filterProps: ['flex'],
  })(
    ({flex}) => (flex ? {display: 'flex'} : undefined),
  )
}

const MyComponent = withFlex(props => (<div {...props} />))

<MyComponent flex />
// this will render a <div> element with the display CSS property set to 'flex'.
// The withFlex HOC can't set a `rootEl` because it accepts all kind of components.
// The flex property will not be forwarded to the div because it is listed in
// `filterProps`.

Another use case for filterProps is to use it in conjunction with propsAreCssOverrides. If propsAreCssOverrides is set to true all component properties are converted to styles and will be put into the generated CSS. To avoid adding unknown CSS properties and being able to add styles dynamically the properties used to generate the dynamic styles can be included in filterProps.

const MyComponent = props => (<div {...props} />)

const MyStyledComponent = glamorous(MyComponent, {
  rootEl: 'div',
  filterProps: ['big'],
  propsAreCssOverrides: true,
})(
  ({big}) => ({fontSize: big ? 36 : 24})
)

<MyStyledComponent big margin={2}>Hello World</MyStyledComponent>

shouldClassNameUpdate

Most of the time, glamor is super fast, but in some scenarios it may be nice to prevent glamor from computing your styles when you know the class name should not change. In these cases, you can implement shouldClassNameUpdate. For example:

const pureDivFactory = glamorous('div', {
  shouldClassNameUpdate(props, previousProps, context, previousContext) {
    // return `true` to update the classname and
    // `false` to skip updating the class name
    return true
  },
})
const Div = pureDivFactory({marginLeft: 1})
render(<Div css={{marginLeft: 2}} />)
// this will render:
// <div />
// with {marginLeft: 2}

Note that this is not the same as shouldComponentUpdate. Your component will still be rerendered. shouldClassNameUpdate is only for allowing you to opt-out of generating the className unnecessarily.

propsAreCssOverrides

This allows you to use props as CSS. You always have the css prop, but sometimes it's really nice to use just the props as CSS.

const MyDiv = glamorous('div', {propsAreCssOverrides: true})({
  margin: 1,
  fontSize: 1,
})
render(<MyDiv margin={2} css={{':hover': {fontWeight: 'bold'}}} />)
// renders <div /> with margin: 2, fontSize: 1, and fontWeight: bold on hover

You can also compose the built-in components: glamorous(glamorous.Div)(/* styles */)

withComponent

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.

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 in glamorous(), or regular className prop).

withProps

Sometimes it can be useful to apply props by default for a component. The simplest way to do this is by simply setting the defaultProps value on the glamorousComponent. But if you want a little more power and composition, then the withProps APIs can help.

These APIs are highly composable, it would be hard to show you all the examples of how this composes together. Just know that it behaves as you might expect.

// when creating a glamorousComponentFactory
const bigDivFactory = glamorous('div', {withProps: {big: true}})
const BigDiv = bigDivFactory(({big}) => ({fontSize: big ? 20 : 10}))
render(<BigDiv />) // renders with fontSize: 20
render(<BigDiv big={false} />) // renders with fontSize: 10

// applying props to an existing component
const MyDiv = glamorous.div(({small}) => ({fontSize: small ? 10 : 20}))
const SmallDiv = MyDiv.withProps({small: true})
render(<SmallDiv />) // renders with fontSize: 10

Based on those examples, there are three places you can apply props to a glamorous component. How these props are composed together applies in this order (where later has more precedence):

  1. Creating a glamorousComponentFactory
  2. Directly on a glamorousComponent with the .withProps function
  3. When rendering a component (just like applying props to a regular components)

In addition to this, you can also have dynamic props. And these props don't have to be used for glamorous styling, any valid props will be forwarded to the element:

const BoldDiv = glamorous
 .div(({bold}) => ({fontWeight: bold ? 'bold' : 'normal'}))
 .withProps(({bold}) => ({className: bold ? 'bold-element' : 'normal-element'}))

render(<BoldDiv />) // renders <div class="bold-element" /> with fontWeight: bold
render(<BoldDiv bold={false} />) // renders <div class="normal-element" /> with fontWeight: normal

The .withProps API can also accept any number of arguments. They are called with (accumulatedProps, context). accumulatedProps refers to the props that are known so far in the accumulation of the props which makes this API highly composable. Finally, the withProps APIs can also accept arrays of objects/functions. You can pretty much do anything you want with this API.

NOTE: This is a shallow merge (uses Object.assign)

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: 1px; padding: 4px; }
<MyStyledDiv noPadding /> // styles applied: { margin: 1px; padding: 0; }

Tip

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 and glamor will treat that exactly the same. It's really expressive!

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.

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: 1px;
  padding-right: 2px;
  padding-bottom: 3px;
  padding-left: 4px;
}
  as well as 'not-active' and anything coming from `extra-thing`.
*/

TypeScript

่ฟ™ไปฝๆ–‡ๆกฃ่ฟ˜ๆฒกๆœ‰็ฟป่ฏ‘ใ€‚ไฝ ่ƒฝๅธฎๆˆ‘ไปฌ็ฟป่ฏ‘ๅ—๏ผŸ

Using the glamorous TypeScript definitions

The bundled typescript definitions are based around the needs of the developers who contributed them and may be missing recent features.

Pull requests to improve them are welcome and appreciated. If you've never contributed to open source before, then you may find this free video course helpful.

Using the typings

The glamorous definitions require typescript version 2.4 or above.

glamorousComponentFactory

// Creating your own
glamorous(Component)(/* styleArgument */)
glamorous('div')(/* styleArgument */)

// Using built-in
glamorous.div<Props>(/* styleArgument */)

// Using shouldClassNameUpdate
glamorous(Component, {
  shouldClassNameUpdate: (props, prevProps, context, prevContext) => props !== prevProps
})(/* styleArgument */)

// Using shouldClassNameUpdate with Context
glamorous<Props, Context>(Component, {
  shouldClassNameUpdate: (props, prevProps, context, prevContext) => context !== prevContext
})(/* styleArgument */)

// Using withProps
glamorous(Component, {
  withProps: {primaryColor: 'red'}
})((props) => ({/* props = { primaryColor: string } */})

const WithPropsComponent = glamorous(Component)(/* styleArgument */).withProps(withProps: {primaryColor: 'red'})
...
<WithPropsComponent primaryColor='' /> // primaryColor is an optional prop of string type based on the above

glamorousComponentFactory arguments

By providing the typings for Props and Theme to Glamorous when setting up your component factory they will be typed on the props argument for function arguments automatically.

interface Props {
  noPadding?: boolean,
  theme: { color: string }
}

const MyStyledDiv = glamorous.div<Props>(
  {
    margin: 1,
  },
  ({noPadding, theme}) => ({
    padding: noPadding ? 0 : 4,
    color: theme.color,
  })
)

<MyStyledDiv /> // styles applied: {margin: 1, padding: 4}
<ThemeProvider theme={{color: 'red'}}>
  <MyStyledDiv noPadding /> // styles applied: {margin: 1, padding: 0, color: red}
</ThemeProvider>

Incomplete support

CSS property safety

  • pseudo-class
  • pseudo-element
  • Relational CSS Selectors
  • Media Queries

All of these work, however you only get typesafety and intellisense on simple css key props (see the css typings).

In the future this may become possible with Microsoft/TypeScript#6579

Alternatively support for full typesafety would be possible using patterns along the lines of http://typestyle.io/.

Known Issues

Built-in Glamorous Components

Whilst you have typesafety on known properties, any misspelt or missing CSSProperties will pass validation.

ie.

<P bakgroundColor="...">

Generating Definition files

When using glamorous in a library that you are generating definition files for you may need to include the following import and export to get around a typescript issue Microsoft/TypeScript/issues/5938.

import glamorous, { GlamorousComponent  } from 'glamorous'
export { GlamorousComponent }