@abstractpoint/subatomic

Inline style props for emotion and styled-components

Usage no npm install needed!

<script type="module">
  import abstractpointSubatomic from 'https://cdn.skypack.dev/@abstractpoint/subatomic';
</script>

README

subatomic
Inline style props for emotion and styled-components.
Spend less time naming things. Iterate faster โšก๏ธ



Subatomic allows you to style your React components inline so that you can spend more time writing styles and less time thinking of new component names. It integrates with emotion and styled-components so that you have the best of both worlds: The power of your favorite css-in-js library plus an inline style system to help you move fast and try out new ideas.

๐Ÿ’ Basic Usage

npm install --save subatomic

Supercharge any component by making subatomic('element') the root element.

import styled from 'emotion';
import subatomic from 'subatomic/emotion';

// Our components
const Box = subatomic('div');
const H1 = subatomic('h1');
const Button = styled(subatomic('button'))`
  color: white;
  background-color: blue;
  &:hover {
   background-color: lightblue;
  }
`

// Now we have style props! Tweak any style inline.
<Box padding="20px" backgroundColor="white">
  <H1 fontSize="48px" fontWeight="700">
    My Awesome Website
  </H1>
  <Box fontSize="32px" fontWeight="300">
    All other websites are significantly less awesome
  </Box>
  <Button backgroundColor="green">
    Get Started
  </Button>
</Box>

If you use styled-components import from subatomic/styled-components instead.

While that's all you need to know to get started, we also support responsive styles, custom prop logic, pseudo-classes and dynamic elements. Read on to see how each of these features would affect the code example above.

๐Ÿ“ฒ Responsive Style Props

So let's say you're happy with the awesome website header but everything is way too big on mobile. With just a few tweaks to our example above we can decrease padding and font size on smaller screens.

<Box padding={["10px", "20px"]} backgroundColor="white">
  <H1 fontSize={["32px", "48px"]} fontWeight="700">
    My Awesome Website
  </H1>
  <Box fontSize={["24px", "32px"]} fontWeight="300">
    All other websites are significantly less awesome
  </Box>
  <Button backgroundColor="green">
    Get Started
  </Button>
</Box>

As you can see we're now passing an array of values to the padding and fontSize props. These map to an array of screen widths (or "responsive breakpoints"). Subatomic uses a default set of breakpoints, so the above example works without any extra configuration, but you can also override them right in your website theme.

// theme.js
export default {
  // These are the default breakpoints
  breakpoints: ['576px', '768px', '992px', '1200px']
}

// App
import { ThemeProvider } from 'emotion-theming'
import theme from './theme.js'

const App = props => (
  <ThemeProvider theme={theme}>
    {/* ... */}
  </ThemeProvider>
)

Once your theme is made available via ThemeProvider subatomic will automatically use those values instead. For more info about theming see the ThemeProvider docs for emotion or styled-components.

๐ŸŒˆ Custom Style Props

Want to use shorter prop names or hook them into your design system? We make that easy as well. In this example we're using some custom props with shorter names (because less typing is cool) and the prop values now map to these locations in our theme: theme.spacing[i], theme.fontSizes[i], theme.colors.green[2].

<Box p={[2, 3]} bg="white">
  <H1 f={[5, 6]} fontWeight="700">
    My Awesome Website
  </H1>
  <Box f={[4, 5]} fontWeight="300">
    All other websites are significantly less awesome
  </Box>
  <Button bg="greens.2">
    Get Started
  </Button>
</Box>

In the configuration section we'll show you how actually set this up, but the basic idea is that you can quickly define styles inline without giving the up the wonderful consistency of a design system.

๐Ÿ‘ป Psuedo-classes

Use any psuedo-class or pseudo-element by prepending it to the prop name. Here we modify <Button> so that its hover color fits better with its green background-color.

<Button bg="greens.2" hoverBg="greens.1">
   Get Started
</Button>

You can even chain multiple pseudo-classes together. For example: hoverPlaceholderColor

๐ŸŽฉ Dynamic Elements

Sometimes you want to change the underlying element. You can do that with the is prop. In the example below we've renamed H1 to Heading and now use the is prop to make the subheading an h2 element. We also made Button an a element because we want it to be a link that looks like a button.

const Heading = subatomic('h1');

<Box p={[2, 3]} bg="white">
  <Heading f={[5, 6]} fontWeight="700">
    My Awesome Website
  </Heading>
  <Heading is="h2" f={[4, 5]} fontWeight="300">
    All other websites are significantly less awesome
  </Heading>
  <Button is="a" href="/start" bg="greens.2" hoverBg="greens.1">
    Get Started
  </Button>
</Box>

You can even pass a component. Example: <Button is={RouterLink} to="/start">Get Started</Button>

๐Ÿคนโ€โ™€๏ธ Tips and Tricks

Composition Is Your Friend

If you decide you want to turn a chunk of code into a named component you can of course re-write using styled() syntax, but consider using composition instead and pass along props using {...props} (spread syntax).

// Our example from above
<Box p={[2, 3]} bg="white">
  <Heading f={[5, 6]} fontWeight="700">
    My Awesome Website
  </Heading>
  <Heading is="h2" f={[4, 5]} fontWeight="300">
    All other websites are significantly less awesome
  </Heading>
  <Button is="a" href="/start" bg="greens.2" hoverBg="greens.1">
    Get Started
  </Button>
</Box>

// โฌ‡ Becomes a reusable component

const PageHeading ({ title, subtitle, ...props }) => (
  <Box p={[2, 3]} bg="white" {...props}>
    <Heading f={[5, 6]} fontWeight="700">
      {title}
    </Heading>
    <Heading is="h2" f={[4, 5]} fontWeight="300">
      {subtitle}
    </Heading>
  </Box>
);

// Lets also break the button out into its own component
const GreenButton = props => (
  <Button 
    bg="greens.2" 
    hoverBg="greens.1" 
    {...props} 
  />;
);

// โฌ‡ Which is rendered like so

<PageHeading
  title="My Awesome Website"
  subtitle="All other websites are significantly less awesome"
  // We can still pass in style props
  textAlign="center"
/>

<GreenButton is="a" href="/start">
  Get Started
</GreenButton>

Fall Back to Styled When Needed

Subatomic builds on emotion and styled-components so that you always have their styling syntax to fall back on when needed. The goal is to help you work faster, not completely change your workflow. Here are some cases where you might want to just create a normal styled() component (or use emotion's css prop).

  • You need to do do css animations with @keyframes
  • Component has a lot of hover styles and props like hoverPlaceholderColor are getting unwieldy
  • Cases where you need more advanced media queries (such as using both min and max width in one rule)
  • You'd rather just use subatomic for spacing and layout components

๐Ÿค– Theme Configuration

Subatomic will automatically use the following default theme which comes with a basic style system and some useful custom props. See the inline comments below for an explanation of each property and a code example at the bottom that shows how to extend this theme to add your own style system and props.

export default {
  breakpoints: ['576px', '768px', '992px', '1200px'],
  space: [0, 4, 8, 16, 32, 64, 128, 256, 512 ],
  fontSizes: [12, 14, 16, 20, 24, 32, 48, 64, 72],
  // Custom style props go in the props object
  props: {
    f: {
      // Where to find values in theme
      themeKey: 'fontSizes',
      // Default unit if none specified
      defaultUnit: 'px',
      // Resulting css property
      style: 'fontSize'
    },
    color: {
      // Extend theme and add colors object
      themeKey: 'colors',
      style: 'color'
    },
    bg: {
      themeKey: 'colors',
      style: 'backgroundColor'
    },
    borderColor: {
      themeKey: 'colors',
      style: 'borderColor'
    },
    d: {
      style: 'display'
    },
    p: {
      themeKey: 'space',
      defaultUnit: 'px',
      style: 'padding',
      // Directional variations
      variations: {
        pt: 'paddingTop',
        pr: 'paddingRight',
        pb: 'paddingBottom',
        pl: 'paddingLeft',
        px: ['paddingLeft', 'paddingRight'],
        py: ['paddingTop', 'paddingBottom']
      }
    },
    m: {
      themeKey: 'space',
      defaultUnit: 'px',
      style: 'margin',
      variations: {
        mt: 'marginTop',
        mr: 'marginRight',
        mb: 'marginBottom',
        ml: 'marginLeft',
        mx: ['marginLeft', 'marginRight'],
        my: ['marginTop', 'marginBottom']
      }
    },
    h: {
      style: 'height'
    },
    // Advanced width prop
    w: {
      // Style is a function instead of a string
      style: value => {
        let width = value;
        // If less than 1 make it a fraction of 100% (1/3 = 33.33...%, etc)
        // Nice for column widths (<Row><Col w={1/3}><Col w={2/3}></Row>)
        if (isNumber(width) && width <= 1) {
          width = `${width * 100}%`;
        }
        return {
          width: width
        };
      }
    }
  }
}

Extending the Default Theme

Here's how you'd extend the above theme to add a colors object for the default colors prop to read from, as well as a custom prop for dealing with fonts.

import defaultTheme from "subatomic/themes/default";

export default {
  breakpoints: defaultTheme.breakpoints,
  space: defaultTheme.space,
  fontSizes: defaultTheme.fontSizes,
  fonts: {
    primary: 'avenir, -apple-system, BlinkMacSystemFont',
    monospace: '"SF Mono", "Roboto Mono", Menlo, monospace'
  },
  colors: { 
    greens: ['#84e47b', '#11cc00', '#0da200'] 
  },
  props: {
    ...defaultTheme.props,
    // And then add any other custom props you want
    fontFamily: {
      themeKey: 'fonts',
      style: 'fontFamily'
    },
  }
};

// App
import { ThemeProvider } from 'emotion-theming'
import theme from './theme.js'

const App = props => (
  <ThemeProvider theme={theme}>
    {/* ... */}
  </ThemeProvider>
)

Any theme you make available via ThemeProvider will override the default theme, so you can either extend (merge in the parts you want) or write your own custom theme.

Interested in helping us add new themes that mimic the look and utility classes of various UI kits like tachyons, bulma, etc? Feel free to add to our themes directory in a pull request.

๐Ÿ’ก Inspiration

Subatomic was inspired by these excellent projects: