outclass
v0.3.0
Published
CSS class manipulation tool
Downloads
194
Maintainers
Readme
Outclass
Currently in pre-release version, expect breaking changes.
Outclass is a CSS class manipulation tool. It can be used to create strings of CSS classes dynamically, for example:
import { out } from "outclass";
// Generates a string of unique class names
out.parse("flex", "rounded", "flex");
// flex rounded
// Implements the builder pattern
out.set("blur w-4").remove("w-4").add("shadow w-2").parse();
// blur shadow w-2
// Has a variants system
out.variant({ sm: "p-2", lg: "p-4" }).choose("sm").parse();
// p-2
// Is patchable
const patch = out.remove("m-2").add("m-4");
out.set("border m-2").apply(patch).parse();
// border m-4
// All in a single call
out.parse({
add: "m-2",
apply: patch,
variant: { sm: "p-2", lg: "p-4" },
choose: "sm",
use: (cls) => cls.replace("m-4", "m-8"),
});
// m-8 p-2Installation
Outclass is available on npm as ECMAScript module (ESM) and works on any JavaScript runtime:
# Node.js
npm add outclass
pnpm add outclass
yarn add outclass
# Deno
deno add npm:outclass
# Bun
bun add outclassAnd in the browser:
<script type="module">
import { out } from "https://esm.sh/outclass";
</script>The out object can be imported from the outclass module:
import { out } from "outclass";Documentation
Outclass lets you to create strings of CSS classes. Internally it keeps a list of classes and offers methods to
manipulate it. The parse method returns a single string consisting of a space-separated list of unique CSS classes,
which can be used in the class attribute of HTML elements.
There are two main ways of use, which can be combined as desired:
- A builder: to programmatically add and remove classes
- A variant system: to specify and select style variants
Inputs
Most methods takes as arguments multiple strings and arrays of strings (infinitely nested):
out.parse("flex", ["grow"], [["p-2"]]);
// flex grow p-2Each input string is splitted into single classes on white-spaces:
out.add("flex grow").remove("grow").remove("flex").parse();
// ""It only understand string inputs, but for convenience it also takes, null, undefined and boolean values, so it
can be used with the && operator and ?. optional chaining:
const isMuted = false;
const props = { classes: "bg-slate-700" };
out.parse([
isMuted && "cursor-not-allowed",
props?.classes,
[null, undefined, false, true],
]);
// bg-slate-700Immutability
The out object is immutable, each call to its methods returns a new instance:
const style = out.add("text-end");
out.parse();
// ""
style.parse();
// text-endThis makes it more convenient as you can use it directly in method chaining without the need to create new instances of it every time:
out.set("box-content p-4").apply(out.remove("p-4")).parse();
// box-contentUsing the builder
The out object implements the builder pattern, the add and remove methods add and remove classes respectively,
the set method replaces the current list of classes with a new one:
out.add("flex").parse();
// flex
out.add("grow p-2").remove("grow").parse();
// p-2
out.add("flex").set("p-2").parse();
// p-2Using variants
The variant method is used to specify variants of style, for example a size variant can have options small and
large and a color variant can have options violet and blue. The choose method selects which variants to use.
Only one option per variant can be selected at a time:
out
.variant({ small: "p-2", large: "p-4" })
.variant({ violet: "bg-violet-500", blue: "bg-blue-500" })
.choose("small violet")
.parse();
// p-2 bg-violet-500Variants can have compound options, which are option with multiple names separated by spaces. Compound options are
selected when all names are passed to the choose method:
out
.variant({ small: "p-2", large: "p-4" })
.variant({ violet: "bg-violet-500", blue: "bg-blue-500" })
.variant({ "small violet": "rounded" })
.choose("small violet")
.parse();
// p-2 bg-violet-500 roundedThe choose method can be called multiple times to change the selected variants, this means that it can be used to
specify the default variant and then change it later:
out
.variant({ small: "p-2", large: "p-4" })
.choose("small")
.choose("large", "small")
.parse();
// p-2Type Safety
Outclass provides first-class TypeScript support for variants. When you define variants, the choose method will automatically provide autocomplete for the valid options and strictly validate your inputs:
const btn = out.variant({ small: "p-2", large: "p-4" });
// ✅ Autocompletes "small" and "large"
btn.choose("small");
// ❌ TypeScript Error: Did you mean 'Error: Variant 'bad' does not exist'?
btn.choose("small bad");You can extract the valid variant options into a reusable TypeScript union using the VariantsOf utility.
import { out, type VariantsOf } from "outclass";
const btn = out.variant({ primary: "bg-blue-500", secondary: "bg-gray-500" });
type BtnVariants = VariantsOf<typeof btn>;
// "primary" | "secondary"Patching
The apply method is used to apply patches to the list of classes. Patches are evaluated last, after manipulation of
the main object are, and the order they are applied is kept. A patch simply is a out object:
const patch = out.remove("m-2").add("m-4");
out.set("border m-2").apply(patch).parse();
// border m-4
const pick = out.choose("small");
out.variant({ small: "p-2", large: "p-4" }).choose("large").apply(pick).parse();
// p-2Using modifiers
The use method takes a modifier function that intercepts the final space-separated string of classes just before it is returned. This is extremely useful to integrate tools like tailwind-merge or to apply custom transformations:
out
.set("btn bg-primary text-white")
.use((cls) => cls.replaceAll("primary", "secondary"))
.parse();
// btn bg-secondary text-white
import { twMerge } from "tailwind-merge";
out.set("p-2 p-4").use(twMerge).parse();
// p-4All in a single call
All functionality can be combined in a single call to the with method, which takes an object where each key is a
method name and the value is the arguments to pass to that method:
// All in a single call
out
.with({
set: "grid grow",
remove: "grid",
add: "flex",
variant: { small: "p-2", large: "p-4" },
choose: "large",
use: (cls) => cls.replace("p-4", "p-8"),
})
.parse();
// grow flex p-8The parse method
The parse can be called with no arguments to get the current list of classes, but it can also be called with a
string, in which case it acts as the add method, or with an object, in which case it acts as the with method:
out.set("flex").parse("grow");
// flex grow
out.set("flex").parse({
add: "m-4",
variant: { sm: "p-2", lg: "p-4" },
choose: "sm",
});
// flex m-4 p-2TailwindCSS
Outclass is especially useful when used with atomic or utility-first CSS frameworks such as TailwindCSS. To enable VS
Code IntelliSense for TailwindCSS classes, add this regex to your .vscode/settings.json:
{
"tailwindCSS.experimental.classRegex": [
// Enable IntelliSense on Outclass method calls outside "className" and "class" attributes
[
"\\.(?:parse|add|remove|set|with|variant)\\s*\\(\\s*([\\s\\S]*?)\\s*\\)\\s*",
"[\"'`]([^\"'`]*)[\"'`]",
],
],
}Contributing
Use the included docker compose configuration to spin up the development environment:
docker compose upThis will create a container named outclass, install node dependencies and start the server which will watch for changes in the source code and run tests.
Debugging
Debugging is available throw a typescript REPL that exposes the out object. If you are using VSCode, you can start the REPL and attach to the debugger by running the Debug REPL configuration from the "Run and Debug" view, execution will pause at breakpoints now.
Devcontainer
The project includes a devcontainer configuration for VSCode. To use it, open the project in VSCode and select "Reopen in Container" from the command palette. Beware that the .gitconfig used by the devcontainer may be different from your global config.
Acknowledgements
Inspiration for this project comes mainly from the amazing job done by cva and clsx.
