onno v2.2.0
Tiny (596B) utility for composing class variants using clsx
pnpm add onnoFeatures
- :rocket: Framework agnostic
- :microscope: Single tiny dependency on
clsx(330B) - :yum: Written in TypeScript with delicious type helpers
- :100: Rigorously tested with 100% code coverage
- :confetti_ball: Perfect companion to Tailwind CSS
Usage
import { onno } from "onno"
const button = onno({
variants: {
size: {
sm: "h-8 px-1",
md: "h-10 px-2",
lg: "h-12 px-3",
},
intent: {
primary: "bg-blue-600 text-white",
secondary: "bg-gray-200 text-black",
},
disabled: "opacity-50",
},
})
// "h-10 px-2 bg-blue-600 text-white opacity-50"
const classes = button({ size: "md", intent: "primary", disabled: true })Variants
Define variant names and the classes to be applied to them using the variants config option:
const button = onno({
variants: {
// This `boolean` variant is applied when `disabled === true`
disabled: "access denied", // Classes can be defined as a `string`
// This `boolean` variant is applied when `hidden === true`
hidden: ["barely", "visible"], // Classes can also be a `string[]`
// This `enum` variant is applied when `size === "sm" || "lg"`
size: {
sm: ["pretty", "small"], // Here we are using a `string[]` class list
lg: "really large", // ...and here we are using a `string` class list
},
},
})
button() // ""
button({}) // ""
button({ size: "sm" }) // "pretty small"
button({ disabled: true }) // "access denied"
button({ hidden: true, size: "lg" }) // "barely visible really large"Note that you cannot use className as a variant key since it is reserved for applying additional classes:
const button = onno({
variants: {
className: "not allowed", // Error: "className" cannot be used as a variant name
},
})Defaults
Default variants can be set using the defaults config option:
const button = onno({
defaults: {
hidden: true,
intent: "secondary",
},
variants: {
hidden: "barely visible",
intent: {
primary: "super punchy",
secondary: "quite bland",
},
size: {
sm: "pretty small",
lg: "really large",
},
},
})
button() // "barely visible quite bland"
button({}) // "barely visible quite bland"
button({ hidden: false }) // "quite bland"
button({ intent: "primary" }) // "barely visible super punchy"
button({ size: "sm" }) // "barely visible quite bland pretty small"Baseline Classes
Common classes can be applied using the baseline config option:
const button = onno({
baseline: "solid base", // Can also use a `string[]` class list
variants: {
size: {
sm: "pretty small",
lg: "really large",
},
},
})
button() // "solid base"
button({}) // "solid base"
button({ size: "lg" }) // "solid base really large"Compound Classes
Apply classes when certain variants are combined using the compound config option:
const button = onno({
variants: {
hidden: "barely visible",
size: {
sm: "pretty small",
md: "kinda normal",
lg: "really large",
},
},
compound: [
{
size: ["sm", "lg"],
className: ["compound", "one"], // Applied when `size === "sm" || "lg"`
},
{
size: "md",
hidden: true,
className: "compound two", // Applied when `size === "md" && hidden === true`
},
],
})
button() // ""
button({}) // ""
button({ size: "md" }) // "kinda normal"
button({ hidden: true }) // "barely visible"
button({ size: "lg" }) // "really large compound one"
button({ size: "md", hidden: true }) // "barely visible kinda normal compound two"Additional Classes
Additional classes can be applied using the className option:
const button = onno({
baseline: "solid base",
variants: {
size: {
sm: "pretty small",
lg: "really large",
},
},
})
button() // "solid base"
button({ className: "with more" }) // "solid base with more"
button({ className: "with more", size: "sm" }) // "solid base pretty small with more"Class Composition
Classes are applied in the following order:
baselinevariantscompoundclassName
Under the hood onno uses clsx to build the class list (see clsx docs)
For convenience clsx is exported from onno so you can use it to compose classes:
import { onno, clsx } from "onno"
const button = onno({
variants: {
size: {
sm: "pretty small",
lg: "really large",
},
},
})
clsx("foo", ["bar", { baz: true }], button({ size: "sm" })) // "foo bar baz pretty small"Note that onno's className option also accepts any clsx.ClassValue so you can do:
import { onno, clsx } from "onno"
const button = onno({
variants: {
size: {
sm: "pretty small",
lg: "really large",
},
},
})
button({ size: "lg", className: ["foo", ["bar"], { baz: true }] }) // "really large foo bar baz"TypeScript
Use the OnnoProps type to infer variant props from an OnnoFunction
import { onno, type OnnoProps } from "onno"
export const button = onno({
variants: {
disabled: "not allowed",
size: {
sm: "pretty small",
lg: "really large",
},
},
})
export type ButtonProps = OnnoProps<typeof button>
export type ButtonSizeType = ButtonProps["size"] // "sm" | "lg" | undefined
export type ButtonDisabledType = ButtonProps["disabled"] // boolean | undefinedNote that inferred OnnoProps also include the className option alongside the variants:
export type ButtonClassNameType = ButtonProps["className"] // clsx.ClassValueBy default all variants inferred by OnnoProps are optional. To require one or more variants, pass a union of required variant keys as the second argument to the OnnoProps generic type:
import { onno, type OnnoProps } from "onno"
export const button = onno({
variants: {
disabled: "not allowed",
intent: {
primary: "super punchy",
secondary: "quite bland",
},
size: {
sm: "pretty small",
lg: "really large",
},
},
})
// Require both the `intent` and `size` variants
export type ButtonProps = OnnoProps<typeof button, "intent" | "size">
// Error: Property 'intent' is missing in type '{ size: "md" }'
const buttonProps: ButtonProps = { size: "md" }Tailwind CSS
If you are using the Tailwind CSS VSCode extension, add the following configuration to your workspace .vscode/settings.json file:
{
"tailwindCSS.experimental.classRegex": [
["onno|clsx\\(([^)]*)\\)", "[\"'`]([^\"'`]*)[\"'`]"]
]
}This will enable Tailwind's IntelliSense for both onno and clsx within your project! :tada:

License
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
9 years ago