DEV Community

Cover image for Create reusable and extensible styled elements in one line
Aron
Aron

Posted on

Create reusable and extensible styled elements in one line

Features

Vanilla.js, React, Vue.js, 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?

😰 Traditionally, 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>
    )
}
Enter fullscreen mode Exit fullscreen mode

🥳 Now 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`
Enter fullscreen mode Exit fullscreen mode

Then apply it as usual:

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

It will be rendered as:

<button class="inline-flex font:14 uppercase">submit</button>
Enter fullscreen mode Exit fullscreen mode

Getting Started

Install the package npm i class-variant, npm i @master/styled.react, or npm i @master/styled.vue depending on your framework:

Vanilla JS

import cv from 'class-variant'

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

btn(props) // -> string
Enter fullscreen mode Exit fullscreen mode

React, Vue

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

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

<Button {...props}>
Enter fullscreen mode Exit fullscreen mode

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>
)
Enter fullscreen mode Exit fullscreen mode
<div class="flex text:center uppercase">Hello World</div>
Enter fullscreen mode Exit fullscreen mode

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 />
)
Enter fullscreen mode Exit fullscreen mode
<button class="flex font:12 size:8x opacity:.5" disabled></button>
Enter fullscreen mode Exit fullscreen mode

⚠️ 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 />
)
Enter fullscreen mode Exit fullscreen mode
<button class="font:14 size:12x"></button>
Enter fullscreen mode Exit fullscreen mode

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" />
)
Enter fullscreen mode Exit fullscreen mode
<div class="inline-flex text:center fg:white bg:blue"></div>
Enter fullscreen mode Exit fullscreen mode

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>
)
Enter fullscreen mode Exit fullscreen mode
<button class="inline-flex">Not matched</button>
<button class="inline-flex">Not matched</button>
<button class="inline-flex uppercase">Matched</button>
Enter fullscreen mode Exit fullscreen mode

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>
)
Enter fullscreen mode Exit fullscreen mode
<p class="a">A</p>
<p class="a b">B</p>
Enter fullscreen mode Exit fullscreen mode

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>
)
Enter fullscreen mode Exit fullscreen mode
<button class="inline-flex">button</button>
<a class="inline-flex" href="#example">link</a>
Enter fullscreen mode Exit fullscreen mode

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>
)
Enter fullscreen mode Exit fullscreen mode
<p class="a">A</p>
<p class="b">B</p>
<p class="c">C</p>
<p class="a b c d">D</p>
Enter fullscreen mode Exit fullscreen mode

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'
})
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Community

The Master community can be found here:

Our 《 Code of Conduct 》 applies to all Master community channels.

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.

Top comments (0)