Skip to content

front-of-house/hypostyle

Repository files navigation

hypostyle

npm version test coverage npm bundle size

Minimalist 5th Generation CSS-in-JS built for concision and extension. Fast af, powered by nano-css.

npm i hypostyle

Why

Typical 4th gen CSS-in-JS can be very verbose. hypostyle provides a way of authoring CSS in Javascript using shortcuts and abbreviations — like atomic CSS and frameworks like Tailwind — at a fraction of the bundle size. hypostyle is also framework-agnostic, meaning it can be adapted into any UI framework and any CSS-in-JS library that supports style objects.

Full Example
import { hypostyle } from 'hypostyle'

const { css } = hypostyle({
  breakpoints: ['400px', '800px', '1200px'],
  tokens: {
    color: {
      primary: '#ff4567',
    },
    space: [0, 4, 8, 12, 16, 20, 24, 28, 32],
  },
  shorthands: {
    c: ['color'],
    px: ['paddingLeft', 'paddingRight'],
  },
})

const classname = css({
  c: 'primary',
  px: [4, 8],
})

function Component() {
  return <div className={classname} />
}

👇

color: #ff4567;
padding-left: 16px;
padding-right: 16px;
@media (min-width: 400px) {
  padding-left: 32px;
  padding-right: 32px;
}

Contents

Getting Started

The hypostyle() function is used to configure a theme and return an instance. Your theme can contain the following properties:

  • tokens - design system tokens, like colors, spacing, etc
  • breakpoints - array of breakpoint widths (optional)
  • shorthands - abbreviated properties that map to one or more CSS properties and (optionally) design tokens
  • macros - boolean properties mapped to pre-defined style objects
  • variants - named groups of pre-defined style objects
  • properties - config supported CSS properties

Presets

Hypostyle includes optional preset objects with a starter set of tokens, shorthands, and macros. Source code.

import * as presets from 'hypostyle/presets'

const { css } = hypostyle(presets)

Responsive Values

All values can be responsive by passing an array of values. This array maps to the breakpoints array on your theme. The first value is mobile, and the remaining values are convered into @media breakpoints.

css({
  d: ['block', 'none'],
})

Will generate:

.__3sxrbm {
  display: block;
}
@media (min-width: 400px) {
  .__3sxrbm {
    display: none;
  }
}

Alternatively — and useful if you want to only specify a single breakpoint — you can use object syntax. Just use the indices as object keys:

css({
  d: { 1: 'none' },
})
@media (min-width: 400px) {
  .__3sxrbm {
    display: none;
  }
}

Tokens & Shorthands

Tokens are either objects of named values, or arrays (scales) of values.

const { css } = hypostyle({
  tokens: {
    color: {
      primary: '#ff4557',
    },
    space: [0, 4, 8, 12, 16],
  },
})

Tokens are associated with configurable CSS properties so that you can use token values in your styles. By default, most CSS properties are supported and are self referencing.

Shorthands are like shortcuts that allow you to abbreviate CSS these properties. The rest of the above example could look like this:

const { css } = hypostyle({
  tokens: {
    color: {
      primary: '#ff4557',
    },
    space: [0, 4, 8, 12, 16],
  },
  shorthands: {
    bg: 'background',
  },
})

Which can be used like this, because background is associated with the color tokens by default:

css({ bg: 'primary' }) // => { background: '#ff4567' }

Macros

macros are simple boolean values that expand to be full style objects. The style objects can use any shorthands or tokens you have configured.

const { css } = hypostyle({
  macros: {
    cover: { top: 0, bottom: 0, left: 0, right: 0 },
  },
})

css({ cover: true }) // => { top: 0, bottom: 0, ... }

These are most helpful when used with JSX (via React, hyposcript, or otherwise) i.e.:

<Box cover />

Variants

Slightly higher-level than macros are variants, which allow you to define named style blocks based on property values. Again, your style blocks here can use any shorthands and tokens you've configured.

import * as presets from 'hypostyle/presets'

const { css } = hypostyle({
  ...presets,
  variants: {
    appearance: {
      link: {
        c: 'blue',
        textDecoration: 'underline',
      },
    },
  },
})

Which look like this when used:

css({ appearance: 'link' }) // => { color: 'blue', textDecoration: 'underline' }

Writing CSS

Pseudos & Nested Selectors

const link = css({
  color: 'black',
  '&:hover': {
    color: 'blue',
  },
  '.icon': {
    transform: 'translateX(5px)',
  },
})

Global Styles

Also included alongside css is injectGlobal.

import { hypostyle } from 'hypostyle'
import * as presets from 'hypostyle/presets'

const { injectGlobal } = hypostyle(presets)

injectGlobal({
  'html, body': {
    c: '#333',
    boxSizing: 'border-box',
  },
})

Keyframes

Also also included alongside css is keyframes.

const { keyframes } = hypostyle(presets)

const animation = keyframes({
  '0%': {
    opacity: 0,
  },
  '100%': {
    opacity: 1,
  },
})

css({
  animation: `${animation} 1s linear infinite`,
})

Configuring CSS Properties

Hypostyle comes with built-in support for most CSS properties. To add additional support, have a look at the properties.js file for API, and pass your additional props to the constructor. Example:

const hypo = hypostyle({
  ...,
  tokens: {
    ...,
    radii: ['4px', '8px']
  },
  shorthands: {
    ...,
    bTLR: 'borderTopLeftRadius'
  },
  properties: {
    borderTopLeftRadius: {
      token: 'radii'
    }
  }
})

Usage:

hypo.style({ bTLR: 1 }) // => { borderTopLeftRadius: '8px' }

Server Usage

hypostyle is isomorphic!

const { css, flush, injectGlobal } = hypostyle(presets)

injectGlobal({ '*': { boxSizing: 'border-box' } })

const classname = css({ c: 'primary' })
const stylesheet = flush()

const html = `
<!DOCTYPE html>
<html>
  <head>
    <style>${stylesheet}</style>
  </head>
  <body>
    <div class="${classname}">Hello world</div>
  </body>
</html>
`

Related

License

MIT License © Sure Thing