scoped-style

Scoped style is a next gen tiny css-in-js library to help you style your components. Use the full power of css that you are used to.


Keywords
react, javascript, css, preact, styled-components, universal, inferno, inferno-js, css-in-js, tiny, hyperapp
License
MIT
Install
npm install scoped-style@1.9.0

Documentation

Scoped Style

Scoped style is a next gen tiny css-in-js library to help you style your components. Use the full power of css that you are used to.

Works With

Installation

npm i scoped-style
// or
yarn add scoped-style

Usage

import scoped from 'scoped-style';

// for react
import React from 'react';
const styled = scoped(React.createElement);
//

// for Preact
import { h } from 'preact';
const styled = scoped(h);
//

// for Hyperapp
import { h } from 'hyperapp';
const styled = scoped(h);
//

// for Infernojs
import { createElement } from 'inferno-create-element';
const styled = scoped(createElement);
//

// define global css
styled.global`
  * {
    margin: 0;
  }

  html,
  body {
    width: 100%;
    height: 100%;
  }
`;

// and scoped css
const Button = styled('button')`
  background: ${props => (props.primary ? 'orange' : 'gray')};
  border: none;
  border-radius: 2px;
  :hover,
  :focus,
  :active {
    padding: 10px;
  }
  @media screen and (max-width: 640px) {
    background: blue;
    :hover,
    :focus,
    :active {
      padding: 5px;
    }
  }
`;

// keyframes
const spin = styled.keyframes`
  to {
    transform: rotate(360deg);
  }
`;

const Loader = styled('div')`
  border: 3px solid hsla(185, 100%, 62%, 0.2);
  border-top-color: #3cefff;
  border-radius: 50%;
  width: 2em;
  height: 2em;
  animation: ${() => spin} 1s linear infinite;
`;

// styling children
const BlueChildren = styled('div')`
  > * {
    color: blue;
  }
`;

const App = ({ children }) => (
  <div>
    <Button primary>Login</Button>
    <Loader />
    <BlueChildren>{children}</BlueChildren>
  </div>
);

// Your rendering code

Configuration

generateID - application code

Scoped style lets you choose the way class names are generated. This is possible via generateID method which can be replaced by your own implementation:

// <componentName>.styles.js

import { h } from 'preact';
import scoped from 'scoped-style';

// These two lines below are responsible for achieving custom class name generation
const defaultGenerateID = scoped.generateID;
scoped.generateID = () => `my-app-prefix-${defaultGenerateID()}`;

const styled = scoped(h);

export const Container = styled('section')`
  padding: 10px;
  color: #000;
`;

export const AppDescription = styled('h1')`
  color: #0f0f0f;
`;

Having added that you should end up with something like this:

Custom class names generation

generateID - test environment

Scoped style lets you choose the way class names are generated. This is possible via generateID method which can be replaced by your own implementation.

In jest environment it can be accomplished by adding setup file to jest.config.js:

 setupFiles: ['./setupFile.js'],
// setupFile.js

import scoped from 'scoped-style';

scoped.generateID = () => `my-app-prefix-${Date.now()}`;

The outcome of your snapshot test would be something like this:

 Snapshot name: `App should render correctly 1`

    - Snapshot
    + Received

    @@ -1,11 +1,11 @@
      <DocumentFragment>
        <section
    -     class="c0 "
    +     class="my-app-prefix-1572168520661 "
        >
          <h1
    -       class="c1 "
    +       class="my-app-prefix-1572168520662 "
          >
            This is test description
          </h1>
          <header>
            <a

Please note that in this case your snapshots will be failing each time you run your tests (different timestamp). That's why it's recommended to stick to the default class name generation strategy which was created to prevent that.

Support and limitations

Combinators

Supported :

Not supported :

Selectors

Selectors must be used in with a combinator.

We support them all :

Pseudo selectors

We support them all.

Warning, if you are using the content property : special characters (like â—€) are not suppoted, you must convert them (via http://enc.joyho.net/ for example) and it will work (content: "\\25C0";).

Questions, bugs and feature requests

You have a question about usage, feel free to open an issue.

You found a bug, feel free to open an issue.

You've got a feature requests, feel free to open an issue.

Advanced example : radio button, checkbox

const Label = styled('label')`
  position: relative;
  padding-left: 2rem;
  padding-right: 0.75rem;
  margin-bottom: 0.75rem;
  cursor: pointer;
  font-size: 1rem;
  user-select: none;

  > input {
    position: absolute;
    opacity: 0;
    cursor: pointer;
    height: 0;
    width: 0;
  }

  > input[type='checkbox'] ~ span {
    position: absolute;
    top: 0.2rem;
    left: 0;
    height: 1rem;
    width: 1rem;
    background-color: #eee;
  }

  > input[type='radio'] ~ span {
    position: absolute;
    top: 0.2rem;
    left: 0;
    height: 1rem;
    width: 1rem;
    background-color: #eee;
    border-radius: 50%;
  }

  :hover > input ~ span {
    background-color: #ccc;
    transition: 0.2s;
  }

  > input:checked ~ span {
    background-color: #0080b3;
  }

  :active > span {
    transform: scale(0);
  }

  > span:after {
    content: '';
    position: absolute;
    display: none;
  }

  > input:checked ~ span:after {
    display: block;
  }

  > input[type='checkbox'] ~ span:after {
    left: 0.25rem;
    top: 0.05rem;
    width: 0.25rem;
    height: 0.6rem;
    border: solid white;
    border-width: 0 0.2rem 0.2rem 0;
    transform: rotate(45deg);
  }

  > input[type='radio'] ~ span:after {
    left: 0.3rem;
    top: 0.3rem;
    width: 0.4rem;
    height: 0.4rem;
    border-radius: 50%;
    background: white;
  }
`;

const Radio = ({ name, checked, onChange }) => (
  <Label>
    {name}
    <input type="radio" checked={checked} onChange={onChange} />
    <span></span>
  </Label>
);

const Checkbox = ({ name, checked, onChange }) => (
  <Label>
    {name}
    <input type="checkbox" checked={checked} onChange={onChange} />
    <span></span>
  </Label>
);

Scoped function second parameter

import scoped from 'scoped-style';

scoped can take a second parameter : a callback who "render" the css generated by styled function.

The default callback is exported via scoped.defaultCallback.

SSR example

Client :

import { createElement } from 'inferno-create-element';
import scoped from './scoped-style';

if (typeof global !== 'undefined') {
  global.scopedStyleCSS == '';
}

const styler = css => {
  if (typeof document !== 'undefined') {
    scoped.defaultCallback(css);
  } else if (typeof global !== 'undefined') {
    global.scopedStyleCSS += css;
  }
};

export const styled = scoped(createElement, styler);

Server :

app.get('*', (req, res) => {
  // first render the root component to string via the SSR function of your framework
  const content = renderToString(<App {...props} />);
  res.send(`
    <!DOCTYPE html>
    <html>

    <head>
      <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1">
      <meta content="text/html; charset=utf-8">
      <title>
        Citral
      </title>
      <style type="text/css">
        ${global.scopedStyleCSS /* then use the generated styles string */}
      </style>
    </head>

    <body>
      ${content}
      <script type="text/javascript" src="${fs
        .readdirSync(path.join(__dirname, 'client'))
        .find(file => /^[a-z0-9]+\.bundle\.js$/.test(file))}"></script>
    </body>

    </html>
  `);
});