styled icon indicating copy to clipboard operation
styled copied to clipboard

Create reusable and extensible styled elements in one line


Master

Create reusable and extensible styled elements in one line

NPM Version NPM package ( download / month ) Discord online Follow @mastercorg Github release actions

Features

Vanilla, Next, React, Vue, Tailwind CSS, and Master CSS are available:

  • ⚑️ Ultra-lightweight ~1.5KB, powered by Proxy
  • πŸ›‘οΈ Type safety
  • 🌈 Dynamically change styles based on properties
  • πŸ’« Re-expand existing elements, like NextLink
  • 🧩 Compatible with server and client components
  • πŸͺ„ Built-in first-class clsx handling

Why?

😰 Before: Creating a simple styled element using a FunctionalComponent is unpleasant.

function Button(props) {
    return (
        <button {...props} className={"inline-flex font:14" + (props.className ? ' ' + props.className : '')}>
            {props.children}
        </button>
    )
}

πŸ₯³ After: It's in one line and ~80% code less.

import styled from '@master/styled.react' // or .vue

const Button = styled.button`inline-flex font:14`

Apply it as usual:

export default function App() {
    return (
        <Button className="uppercase">submit</Button>
    )
}

It will be rendered as:

<button class="inline-flex font:14 uppercase">submit</button>

Getting Started

Install the package depending on your framework:

Vanilla

npm install class-variant
import cv from 'class-variant'

const btn = cv(...params)
const btn = cv`...` // or

btn(props) // -> string

React

npm install @master/styled.react
import styled from '@master/styled.react'

const Button = styled.button(...params)
const Button = styled.button`...` // or

<Button {...props}>

Vue

npm install @master/styled.vue
import styled from '@master/styled.vue'

const Button = styled.button(...params)
const Button = styled.button`...` // or

<Button {...props}>

Basic usage

Create a styled element

Declared in two ways: function or template literal, and parameters inherit all features of clsx.

const Element = styled.div`flex text:center`
const Element = styled.div('flex text:center') // or

return (
    <Element className="uppercase">Hello World</Element>
)
<div class="flex text:center uppercase">Hello World</div>

Apply based on predefined variants

Predefine the class variant corresponding to the property.

const Button = styled.button('flex', {
    $size: {
        sm: 'font:12 size:8x',
        md: 'font:14 size:12x'
    },
    disabled: 'opacity:.5',
    ...
})

return (
    <Button $size="sm" disabled />
)
<button class="flex font:12 size:8x opacity:.5" disabled></button>

⚠️ Custom properties that are not element-owned properties must be prefixed with $prop, otherwise they will be reflected on the final element and an error may be thrown.

You can set default properties for elements.

Button.defaultProps = {
    $size: 'md'
}

return (
    <Button />
)
<button class="font:14 size:12x"></button>

Apply based on function properties

Dynamically apply class names based on function properties.

const Element = styled.div('fg:white',
    ({ $color }) => $color && `bg:${$color}`
)

return (
    <Element $color="blue" />
)
<div class="inline-flex text:center fg:white bg:blue"></div>

Apply based on matching properties

Applies the target class name matching all specified property keys and their values.

const Button = styled.button('inline-flex',
    ['uppercase', { $intent: 'primary', $size: 'md' }]
)

return (
    <Button $intent="primary">Not matched</button>
    <Button $size="md">Not matched</button>
    <Button $intent="primary" $size="md">Matched</button>
)
<button class="inline-flex">Not matched</button>
<button class="inline-flex">Not matched</button>
<button class="inline-flex uppercase">Matched</button>

Extended

Extend a styled element

Extend an existing styled element and add additional classes or conditions.

const A = styled.p('a')
const B = styled.p(A)`b`

return (
    <A>A</A>
    <B>B</B>
)
<p class="a">A</p>
<p class="a b">B</p>

It also supports client components, taking Next.js’s Link as an example:

'use client'

import styled from '@master/styled.react';
import Link from 'next/link';

const Button = styled(Link)`inline-flex text:center`

return (
    <Button>Submit</Button>
)

⚠️ If the extended target contains client components, the 'use client' directive is required.

Change an element's tag name

Changing the original tag name of an element, such as <button> to <a>. Left empty '' even if there are no additional classes.

const Button = styled.button('inline-flex')
const Link = styled.a(Button)``

return (
    <Button>button</Button>
    <Link href="#example">link</Link>
)
<button class="inline-flex">button</button>
<a class="inline-flex" href="#example">link</a>

Extend multiple styled elements

Extend multiple style elements at once.

const A = styled.p`a`
const B = styled.p`b`
const C = styled.p`c`
const D = styled(A)(B)(C)`d`

return (
    <A>A</A>
    <B>B</B>
    <C>C</C>
    <D>D</D>
)
<p class="a">A</p>
<p class="b">B</p>
<p class="c">C</p>
<p class="a b c d">D</p>

Typings

declare type Props = {
    $size: 'sm' | 'md'
}

const Button = styled.button<Props>('flex', {
    $size: {
        sm: 'font:12 size:8x',
        md: 'font:14 size:12x'
    },
    disabled: 'opacity:.5'
})

Overview class-variant APIs

import cv from 'class-variant'

const btn = cv(
    'inline-flex rounded',
    {
        intent: {
            primary: 'bg:blue fg:white bg:blue-60:hover',
            secondary: 'bg:white fg:slate-30 bg:slate-90:hover',
        },
        size: {
            sm: 'text:14 p:5|15',
            md: 'text:16 p:10|25'
        }
    },
    ['uppercase', { intent: 'primary', size: 'md' }],
    ({ indent, size }) => indent && size && 'font:semibold'
)

btn.default = {
    intent: 'primary',
    size: 'sm'
}

btn()
// inline-flex rounded bg:blue fg:white bg:blue-55:hover text:14 p:5|8 font:semibold

btn({ indent: 'secondary', size: 'sm' })
// inline-flex rounded bg:white fg:slate-30 bg:slate-90:hover text:14 p:5|8 font:semibold

btn({ indent: 'primary', size: 'md' })
// inline-flex rounded bg:blue fg:white bg:blue-55:hover text:16 p:10|25 uppercase font:semibold

Community

The Master community can be found here:

Our γ€Š Code of Conduct 》 applies to all Master community channels.

Contributing

Please see our CONTRIBUTING for workflow.

Inspiration

Some of our core concepts and designs are inspired by these giants.

  • Template Literal - The use of template literals as syntactic sugar for reusing components is inspired by Styled Components.