createStyle is a simple utility for creating reusable styled React components focused around CSS classes rather than discrete styles.
It works great for Tailwind based styling frameworks and offers a fast way to stand up a pure component without the heaviness of delivering a full React component.
Basically it's styled-components for Tailwind.
Obviously it's built in Typescript and has both ES Modules and CJS support.
Oh yeah, it doesn't have any dependencies and is super tiny (~1kb minified, ~500b gzipped)
To use createStyle in your project, simply install it with npm or your preferred package manager.
npm install @josephmark/createstyleimport createStyle from "@josephmark/createstyle"
const H1 = createStyle("h1", "text-lg font-serif leading-[1.5]")
const P = createStyle("p", "tracking-wide")
const Header = () => (
<header>
<H1 className="mb-5">createStyle saved my bacon!</H1>
<figure>
<P as="blockquote">
createStyle is a must-have utility for any developer who wants to create
components with Tailwind. Whether you're working on a small project or a
large-scale application, CreateStyle makes it easy to create and manage
your styles, saving you time and headaches in the process.
</P>
<P as="cite" className="opacity-50">
- chatGPT
</P>
</figure>
</header>
)createStyle(defaultTag, classes, defaultProps)
createStyle is a function that accepts three arguments
- Default Tag (required) - the semantic HTML tag, or a React component, you want rendered whenever you use this element (this can be overwritten later)
- Classes (optional) - the CSS class names you want applied to the element by default (these can be appended to later)
- Default Props (optional) - tag props you want to set on the element other than class, including display name (for devtools)
The function returns a React component with forwardRef.
The as prop accepts either an intrinsic HTML tag name or any React component.
import createStyle from "@josephmark/createstyle"
const H3 = createStyle("h3", "text-lg tracking-wide")
const Header = () => (
<H3 as="h1">createStyle is an open-source element generator</H3>
)Pass a component to as to keep your styles while delegating rendering to it. The
merged class string is handed to the component as className, so it needs to accept
and apply that prop.
import Link from "next/link"
import createStyle from "@josephmark/createstyle"
const Button = createStyle("button", "rounded-full bg-red-500 px-4 py-2")
const CTA = () => (
<Button as={Link} href="/signup">
Sign up
</Button>
)The same works for a component as the default tag, so every instance renders through it:
const StyledLink = createStyle(Link, "underline underline-offset-2")
const Nav = () => <StyledLink href="/about">About</StyledLink>Note
Render-time props are typed as standard HTML attributes, so props on the target
component that aren't HTML attributes are rejected by TypeScript. href works
(it's a real attribute); something like react-router's to is not. Wrap the
component or cast in that case.
import createStyle from "@josephmark/createstyle"
const Button = createStyle<HTMLButtonElement>(
"button",
"rounded-full bg-red-500 text-white"
)import createStyle from "@josephmark/createstyle"
const Card = createStyle("article", "rounded p-2 bg-white shadow-lg", {
displayName: "Card",
})import createStyle from "@josephmark/createstyle"
const Checkbox = createStyle("input", "rounded bg-white border border-black", {
type: "checkbox",
})import createStyle from "@josephmark/createstyle"
import clsx from "clsx"
const TogglerEl = createStyle("button", "rounded py-2 px-4", { type: "button" })
const Toggler = () => {
const [isToggled, setIsToggled] = useState(false)
// Classes added during render are appended to the styled element
return (
<TogglerEl
className={clsx(isToggled ? "bg-red-500" : "bg-blue-500")}
onClick={() => setIsToggled((t) => !t)}
>
Toggle me!
</TogglerEl>
)
}import { useRef } from "react"
import createStyle from "@josephmark/createstyle"
const Container = createStyle<HTMLDivElement>("div", "max-w-screen-xl mx-auto")
const MyComponent = () => {
const ref = useRef<HTMLDivElement>(null)
return <Container ref={ref} />
}import type { FC, HTMLAttributes, ReactNode } from "react"
import createStyle from "@josephmark/createstyle"
const P = createStyle("p", "leading-[1.2] text-[1rem]")
const AppendsParagraphClasses: FC<{
children: ReactNode
Tag: FC<HTMLAttributes>
}> = ({ Tag, children }) => {
return <Tag className={P}>{children}</Tag> // or: return <Tag className={P.toString()}>{children}</Tag>
}import type { FC, HTMLAttributes, ReactNode } from "react"
import createStyle from "@josephmark/createstyle"
const Checkbox = createStyle("input", "rounded border border-black", {
style: {
boxShadow: "0 0 0 4px -1px black",
},
})
const CheckboxGroup = () => {
return (
<div style={{ "--checkbox-bg": "red" }}>
<Checkbox
style={{
...Checkbox.props.style,
backgroundColor: "var(--checkbox-bg)",
}}
/>
</div>
)
}import type { FC, ReactNode } from "react"
import createStyle from "@josephmark/createstyle"
const SerifHeading = createStyle("h2", "font-serif text-lg")
const SansHeading = createStyle("h2", "font-sans text-xl tracking-tight")
const THEMES = {
serif: SerifHeading,
sans: SansHeading,
}
type ThemableProps = {
theme: keyof typeof THEMES
children: ReactNode
}
const ThemableComponent: FC<ThemableProps> = ({ theme, children }) => {
const C = THEMES[theme]
return <C>{children}</C>
}