Arto is a type-safe, flexible class name management library designed for building scalable UIs with variants, states, and advanced conditional styling.
For full documentation, visit arto.elgndy.com.
- 🎨 Variants: Cleanly define style options (e.g., size, color) without messy conditional logic.
- 🔄 States: Apply conditional classes for states like
disabled
,hovered
, etc., with optional dependency logic. - ⚡ Rules & Logic: Dynamically add or remove classes using logical operators (
AND
,OR
,XOR
, etc.) or custom callbacks. - 🔧 Fully Extensible: Write or install plugins to extend functionality (e.g., theming, UI framework integration).
- 🛡️ Type-Safe: Built with TypeScript for robust validation and developer confidence.
- ✨ Lightweight: No external dependencies—integrates seamlessly with your chosen framework (React, Vue, Svelte, or plain JS).
- 🌐 Framework Agnostic: Compatible with any CSS strategy (e.g., Tailwind, PostCSS, CSS Modules).
pnpm add arto
Below is a very simplified example to illustrate how Arto might handle variants and states. For more in-depth or real-world examples, see our Documentation and examples/ folder.
import { arto } from 'arto'
// 1. Create an arto instance with basic config
const myArto = arto({
// Always include these classes
className: 'btn',
// Define variants for styling
variants: {
size: {
small: 'btn-sm',
large: 'btn-lg',
},
},
// Define states for toggling
states: {
disabled: 'opacity-50 pointer-events-none',
},
})
// 2. Generate a final class string
const classString = myArto({
variants: { size: 'large' },
states: { disabled: true },
})
// => "btn btn-lg opacity-50 pointer-events-none"
Below is a more complex scenario showcasing:
- Nested state definitions with
dependsOn
- Callback-based class names that leverage user-defined
context
- Conditional logic through the rules
array
import { arto } from 'arto'
// Define variant keys and possible values
type Variants = {
intent: 'info' | 'danger' | 'success'
size: 'sm' | 'md'
}
// Define possible states
type States = 'hovered' | 'disabled'
// Optional context data passed during class generation
type Context = {
username: string
}
const myArto = arto<Variants, States, Context>({
// Base classes
className: 'base',
// Variant definitions
variants: {
intent: {
info: 'intent-info',
danger: {
className: 'intent-danger',
states: {
hovered: {
className: 'intent-danger-hovered',
dependsOn: [{ not: ['disabled'] }], // apply only if 'disabled' is not active
},
},
},
// Callback-based class name
success: (ctx) => (ctx?.username === 'admin' ? 'intent-success-admin' : 'intent-success'),
},
size: {
sm: 'size-sm',
md: 'size-md',
},
},
// Global states
states: {
hovered: {
className: 'global-hovered',
dependsOn: [{ not: ['disabled'] }], // can't hover if disabled
},
disabled: 'global-disabled',
},
// Advanced rules
rules: [
{
when: {
variants: { intent: ['info', 'danger'] },
states: ['hovered'],
logic: 'AND',
},
add: 'rule-class',
},
],
})
// Usage examples:
// A) Info + small (no states)
const exampleA = myArto({
variants: { intent: 'info', size: 'sm' },
})
// => "base rule-class intent-info size-sm"
// B) Danger + hovered + disabled
const exampleB = myArto({
variants: { intent: 'danger', size: 'sm' },
states: { hovered: true, disabled: true },
})
// => "base rule-class intent-danger size-sm global-disabled"
// C) Success + hovered, with admin context
const exampleC = myArto({
variants: { intent: 'success', size: 'md' },
states: { hovered: true },
context: { username: 'admin' },
})
// => "base intent-success-admin size-md global-hovered"
We welcome contributions of all kinds, from bug reports to new features. Before submitting a pull request, please review our Contributing Guide. It covers the recommended workflow, coding standards, and release process to help ensure a smooth collaboration.
This project is released under the MIT License.