A utility-first CSS-in-JS framework built for React. 💅👩‍🎤⚡️

  • By Greg Bergé
  • Last update: Sep 19, 2022
  • Comments: 16

xstyled

A utility-first CSS-in-JS framework built for React.

License npm package npm downloads CircleCI codecov Code style

npm install @xstyled/styled-components styled-components

Docs

See the documentation at xstyled.dev for more information about using xstyled!

Quicklinks to some of the most-visited pages:

Example

import { x } from '@xstyled/styled-components'

function Example() {
  return (
    <x.div p={{ _: 3, md: 6 }} bg="white" display="flex" spaceX={4}>
      <x.div flexShrink={0}>
        <x.img h={12} w={12} src="/img/logo.svg" alt="xstyled Logo" />
      </x.div>
      <x.div>
        <x.h4
          fontSize={{ _: 'md', lg: 'xl' }}
          fontWeight="medium"
          color="black"
        >
          xstyled
        </x.h4>
        <x.p color="gray-500">A CSS-in-JS framework built for React.</x.p>
      </x.div>
    </x.div>
  )
}

License

Licensed under the MIT License, Copyright © 2019-present Greg Bergé.

See LICENSE for more information.

Github

https://github.com/gregberge/xstyled

Comments(16)

  • 1

    Babel Macros - not working

    Hi, it would seem I cannot import the macro version of styled-components:

    https://www.styled-components.com/docs/tooling#babel-macro

    import styled from '@xstyled/styled-components/macro';
    

    Failed to compile. Error: Cannot find module '@xstyled/styled-components/macro'

    Thought trying also importing from @xstyled/macro but no luck.


    Importing directly from styled-components does work well:

    import styled from 'styled-components/macro';
    

    Background:

    We have an app based on create-react-app, not ejected, and not using rewire, because CRA supports babel macros, and I wish to utilize that ability to be able to use the babel-plugin-styled-components plugin.

    I need this so my styled-components will be generated with a real display name, to be able to access components in enzyme unit tests.

    Related thread

  • 2

    New built-in utility: textSize

    🚀 Feature Proposal

    I propose a new builtin textSize utility, that combines font size and line height.

    Motivation

    I love the inspirations xstyled 2 took from Tailwind, especially the new utility style props. One thing that I'm missing is Tailwind's combination of font size and line height, which makes an awful lot of sense. Unless you have very specific design needs, these two properties are always coupled.

    Example

    <x.div textSize="xl">...</x.div> = <x.div fontSize="xl" lineHeight="xl">...</x.div>

    Pitch

    xstyled 2 already has a lot of builtin utilities and a default theme, which is great! I think taking more inspiration from Tailwind can carve out a niche for xstyled among the dozens of low-level CSS-in-JS libraries by providing a more complete utility-based styling solution.

  • 3

    Any future plans on adding types?

    Hey! I've been a long-time user of styled-components and thought I'd give xstyled a try since I could totally relate and understand the motivation behind it!

    But regarding the topic of this issue, would it be possible to import styled-components/styled-system's preexisting type library? And if not, are there future plans to add types?

    Anyways, I really appreciate your work on this library!

  • 4

    Compatibility with react native

    Would be possible to use this project with react-native?. I tried, but I get this error:

    scStyled__default.div is not a function

    I could see under Box.js it's using a div. (https://github.com/smooth-code/xstyled/blob/master/packages/styled-components/src/Box.js#L4)

    Makes sense to create another package to support react-native projects? what do you think? I can try to help

  • 5

    styled.box is different from styled.div`${system}`

    🐛 Bug Report

    I think these should behave the same way, but they don't:

    const Inner = styled.box``
    const Outer = styled(Inner)`color: red;`
    
    // Unexpectedly GREEN
    <Outer color="green">This should be RED</Outer>
    
    const Inner = styled.div`${system};`
    const Outer = styled(Inner)`color: red;`
    
    // Works as expected
    <Outer color="green">This is RED</Outer>
    

    To Reproduce

    https://codesandbox.io/s/funny-brook-cy9ne?file=/src/App.js

    Versions

    This behaves the same way on 1.x and 2.x so it's not a regression, it's just unexpected

  • 6

    override shouldForwardProp

    Summary

    Currently, some props are being passed and written to the DOM. Emotion is handling this by using is-prop-valid which checks if the prop is a valid html attribute before writing. Here

    Test plan

    image

  • 7

    fix(breakpoints): races in useViewportWidth

    Summary

    The main fix here is changing the initial state from null to window.innerWidth, so that there isn't a useless transition from (for example) useDown('md') = true to useDown('md') = false when the application loads. Depending on the application, the rerendering penalty can be heavy.

    While we're here, notice that we can make a couple related changes: First, since the initial state is set properly, we don't need a layout effect, instead we can use a normal effect. Second, swap the order of listener and setWidth to avoid the tiny race between them.

    Test plan

    This cropped up for me in storybook. I have a component that measures its container to make image layout decisions. On the first load of the app, it would work, but on hot reload, the image would be the wrong size.

    The problem is that useDown('md') returns the wrong value the first time. It was okay on initial app load because image loading slowed things down enough that useDown('md') would return the right value before the measurement. However on hot reload, when the image is already loaded, the measurement would fire in the window of time between the bogus useDown and the right useDown.

    Note that the measurement code also reruns on resize, but since there's no resize occurring, it wouldn't know to remeasure.

    Making this change fixed the problem for me.

  • 8

    Add transient Props to @xstyled/emotion

    Summary

    Emotion allows its consumer apps to change styles based on props. See here. By default, emotion would prevent invalid props from being rendered to the DOM element but since xstyled is overriding the "shouldForwardProp" this functionally is being removed.

    Styled-components handle this by prefixing the prop name with a dollar sign $. Transient Props

    Do you see any downside or conflict by doing something this? @agriffis @gregberge

    Test plan

    image

    My previous PR was causing some issues with the "as" so this is why I just remove the ones with the $.

  • 9

    xstyled took a sharp turn into the abomination domain

    as a 2-years "veteran" avid user of xstyled, I saw the new website (and new approach) and was SHOCKED.

    as someone which writes CSS for about 20 years, I believe it is a complete abomination to write styles as props on the component themselves and not keep pure separation of concerns between components (or HTML elements) and their styles.

    I use styled components in a very complex manner and this new approach is far too basic and promotes dirty coding where developers add a mountain of props, all mixed together, some CSS-related and some logic-related, not to mention it's impossible to properly use & read CSS variables (custom properties) this way.

    Your choice where to take xstyled has saddened me greatly and I want to express me feelings so you would know that there is at least one user which is believes this is a mistake.

    Also, the docs are horrible. Cannot find any real information on th utility, or real examples of how it can be used (since I never remember)

    nor can I find any information regarding extending a an already styled component. The "search" bar of the new website really gives poor results.

  • 10

    th utility returns a function and not a value

    (version 1.19.1)

    Hi, I'm trying to create automatic CSS variables out of JSX, with a defined theme file:

    import React from 'react'
    import styled, { getColor } from '@xstyled/styled-components'
    import { getCSSVars } from 'utils' // <-- receives props and convert to CSS variables
    
    const COLORS = {
      blue: getColor('blue') // <- returns a function and not a value
    }
    
    const StyledTag = styled(StyledTagBase)`
      ${getCSSVars('color')}
    `
    
    // using icons from:
    // https://svgbox.net/
    const Tag = ({ children, color = COLORS.blue }) => (
    	<StyledTag color={COLORS[color]} onClick={onClick}>
          {children}
        </StyledTag>
    )
     
    export default Tag
    

    I have a problem that using th or getColor shorthand, returns a function, rather than a value:

    blue: getColor('blue')
    

    What should be done? Can a value be forced out of it?

    Thanks for your kind help!

  • 11

    Generated class names aren't legible

    🐛 Bug Report

    Component class names are not using display names. They look like this:

    <div class="sc-bdVaJa cyWyZX">
      Something
    </div>
    

    To Reproduce

    Here are my dependencies:

    • @xstyled/styled-components 1.16.0
    • @xstyled/system 1.16.0
    • styled-components 4.4.1
    • babel-plugin-styled-components 1.10.7

    Expected behavior

    Previously without xstyled, I would get this class name in my styled components:

    <div class="ComponentDisplayName_Text-sc-bdVaJa cyWyZX">
      Something
    </div>
    
  • 12

    fix: stringify keys on cache

    Summary

    Following this issue https://github.com/gregberge/xstyled/issues/371, I investigated the source code of xstyled to understand what was happening by adding logs:

    // node_modules/@xstyled/system/dist/index.cjs (v3.6.0)
    
    const getStyleFactory = (prop, mixin, themeGet) => {
      return (props) => {
        const fromValue = (value2) => {
          if (!util.is(value2))
            return null;
          if (util.obj(value2))
            return reduceVariants(props, value2, fromValue);
          return util.cascade(mixin(themeGet ? themeGet(value2)(props) : value2), props);
        };
        const value = props[prop];
        if (!util.is(value))
          return null;
        const cache = getCache(props.theme, prop);
        if (cache.has(value)) {
    +     console.log('--- cache.has --- (getStyleFactory)', value)
          return cache.get(value);
        }
        const style2 = fromValue(props[prop]);
    +   console.log('--- cache.set --- (getStyleFactory)', value)
        cache.set(value, style2);
        return style2;
      };
    };
    

    On the server, with each new request, we can see these logs appear:

    --- cache.set --- (getStyleFactory) { md: '60%' }
    --- cache.set --- (getStyleFactory) { md: 2 }
    --- cache.set --- (getStyleFactory) { xs: 'center', md: 'flex-end' }
    --- cache.set --- (getStyleFactory) { md: 'column' }
    --- cache.set --- (getStyleFactory) { md: '80%' }
    --- cache.set --- (getStyleFactory) { xs: 'center', md: 'flex-end' }
    --- cache.set --- (getStyleFactory) { md: '20%' }
    // ...
    

    Basically, an object cannot be used as a key on a map:

    const obj = { xs: 'center', md: 'flex-end' }
    const map = new Map()
    map.set(obj, 'whatever')
    
    const obj2 = { ...obj }
    map.has(obj2) // false
    map.get(obj2) // undefined
    

    So the solution here is to stringify the value when it's an object:

    const key = obj(value) ? JSON.stringify(value) : value
    

    Benchmarks:

    (main)  @xstyled/system x 317,090 ops/sec ±0.61% (90 runs sampled)`
    (fix-map-with-object-keys) @xstyled/system x 261,981 ops/sec ±0.40% (91 runs sampled)`
    

    This fix slows down the benchmark a bit, probably because of the JSON.stringify but prevent memory leaks...

    (maybe we can look at a library to speed up the conversion object -> string, like https://github.com/fastify/fast-json-stringify)

  • 13

    feat: support react-native

    Summary

    Hello guys! basically I'm trying to finalize a PR that was adding support for react-native. Can find out a little more about it here: https://github.com/gregberge/xstyled/pull/273, thanks @diegotsi

    My idea is basically the same as this PR except that I'll be adding the default use without utility props and the x function with utility props.

    I'm also not sure how we're going to configure the tests for it because react-native need different babel preset/jest config module:metro-react-native-babel-preset, I thought I'd use lerna by configuring a babel/jest.config for each package 🤔 @gregberge .

    Test plan

    const WithoutUtilityProps = styled.View`
      background-color: cool-gray-500;
      padding: 2;
    `;
    
    const WithConfig = styled.View.withConfig({})`
      background-color: rose-900;
      padding: 2;
    `;
    
    const StyledSomeComponent = styled(View)`
      background-color: teal-900;
      padding: 2;
    `;
    
    const WithUtilityProps = styled.ViewBox``;
    
    const UsingAS = styled.View``;
    
    const Test = () => {
      return (
        <ThemeProvider theme={defaultTheme}>
          <x.View p={2} bg="blue-gray-900">
            <x.Text color="blue-gray-500">Text X elements</x.Text>
          </x.View>
          <UsingAS as={x.Text} p={2} bg="emerald-500">
            Now is a x.Text with all props from Text (RN) + utility props from
            xstyled
          </UsingAS>
          <WithUtilityProps p={2}>
            <x.Text>With Utility Props</x.Text>
          </WithUtilityProps>
          <WithConfig>
            <x.Text color="white">With Config</x.Text>
          </WithConfig>
          {/*@ts-expect-error */}
          <WithoutUtilityProps bg="black">
            <x.Text>Without Utility Props</x.Text>
          </WithoutUtilityProps>
          <StyledSomeComponent>
            <x.Text color="white">Styled(SomeComponent)</x.Text>
          </StyledSomeComponent>
        </ThemeProvider>
      );
    };
    

    Working with the latest react-native template:

    Captura de Tela 2022-08-21 às 08 32 37

    types

    working

  • 14

    feat: add option to disable xstyled cache

    Summary

    Following this issue https://github.com/gregberge/xstyled/issues/371, we need to have an option to disable xstyled cache. It's already present in styled-system api and can be useful for many reasons.

    Test plan

    Just add the disableXstyledCache key on your theme and set the value to true :

    const theme = {
      ...defaultTheme,
      disableXstyledCache: true,
    }
    
  • 15

    useUp & useDown accept number values but don't support them

    🐛 Bug Report

    Hooks useUp & useDown have following type declaration:

    declare const useUp: (key: string | number) => boolean;
    declare const useDown: (key: string | number) => boolean;
    

    but calling useUp(1024) always returns false.

    To Reproduce

    const App = () => {
      const upLg = useUp(1024);
    
      return <>{upLg ? ">lg" : "<lg"}</>
    }
    
    

    Expected behavior

    useUp & useDown should either disallow number values or handle them properly.

  • 16

    Remove redundant styling props in the next major version of xstyled

    There are a number of redundant props: bg vs backgroundColor, maxW vs maxWidth, maxH vs maxHeight

    On the surface this doesn't seem like a big deal, but if you're making a component library using xstyled which has some base styling and allows overrides by the consumer, this could lead to issues if they don't match using the exact same prop.

    We should choose to favor either the verbose or short versions and deprecate+remove the others to reduce complexity.