Introduction

I’ve always enjoyed building fully accessible UI components with utility-first CSS (Tailwind) and advanced accessibility hooks (like those found in React Aria). But no matter the framework, one problem keeps surfacing: managing complex variants and states in a clean, flexible way.

Existing libraries such as cva and Tailwind Variants are great for smaller use cases, but I frequently hit situations that required advanced logic, dependencies between states, or replacing classes under certain conditions. To solve that, I created Arto—a library that takes a more robust approach while remaining easy to integrate with any UI framework.

What Is Arto?

Arto is a variant- and state-driven class name management library. It helps you define and merge:

  1. Variants (e.g., size: 'small' | 'large', theme: 'primary' | 'secondary'),
  2. States (e.g., disabled, hover, pressed, focus),
  3. Complex Conditional Logic (advanced “rules” for removing or adding classes).

It’s framework-agnostic—use it with React, Vue, Svelte, or plain JavaScript. If you are a Tailwind user, it fits right in with utility classes. If you want to incorporate advanced accessibility hooks or ephemeral states from any library, Arto handles that seamlessly.

Core Features

  • TypeScript-Friendly: Strong typing for variants, states, and optional context.
  • Highly Extensible: Built-in plugin system for theming, conflict linting, analytics, or any custom logic.
  • Context Support: Pass in dynamic context (like user roles) to build class names based on run-time conditions.
  • Rule Engine: If certain variant + state combos occur, you can remove or add classes automatically.

Why Another Library?

  • Flexibility: Many libraries handle typical variant toggles but struggle with deeply conditional logic (e.g., removing base classes when theme=primary and disabled=true).
  • Accessibility: If you’re building accessible components, ephemeral states like hover, focusVisible, or pressed often need precise rules. Arto helps you manage them systematically.
  • Integration: Works with any framework—React, Vue, Svelte, or plain DOM. You can rely on it purely for class string generation.
  • Clean Code: Without a robust solution, you end up scattering conditions throughout your components. Arto centralizes these in a single typed config.

Quick Example (Using a React Snippet)

Here’s a minimal example to illustrate how Arto merges variants and states into a final class string. This snippet uses React for demonstration.

import { arto } from 'arto'

type ButtonVariants = {
  size: 'small' | 'large'
  theme: 'primary' | 'secondary'
}
type ButtonStates = 'disabled'

const buttonConfig = arto<ButtonVariants, ButtonStates>({
  className: 'inline-flex items-center font-medium',
  variants: {
    size: {
      small: 'px-2 py-1 text-sm',
      large: 'px-4 py-2 text-base',
    },
    theme: {
      primary: 'bg-blue-500 text-white',
      secondary: 'bg-gray-200 text-gray-800',
    },
  },
  states: {
    disabled: 'opacity-50 pointer-events-none',
  },
  defaultVariants: {
    size: 'large',
    theme: 'primary',
  },
})

const className = buttonConfig({
  variants: { size, theme },
  states: { disabled },
})

// => "inline-flex items-center font-medium px-2 py-1 text-sm bg-gray-200 text-gray-800"

Handling More Complex Logic

Let’s say you want to remove or replace classes when certain variant-state combos occur. That’s where rules come in:

const buttonWithRules = arto({
  variants: {
    theme: {
      primary: 'bg-blue-500 text-white',
    },
  },
  states: {
    disabled: 'opacity-50 pointer-events-none',
  },
  rules: [
    {
      when: {
        variants: { theme: ['primary'] },
        states: ['disabled'],
      },
      remove: { variants: ['theme'] },
      add: 'bg-blue-300 text-white',
    },
  ],
})

So if theme='primary' and disabled=true, Arto removes the original bg-blue-500 text-white and adds bg-blue-300 text-white. Instead of writing conditionals in your component, you’ve defined a self-contained rule.

Adding Accessibility States

If you want to manage ephemeral states like hover, pressed, or focusVisible from advanced hooks (e.g., React Aria or any other library providing those states), you simply pass booleans to Arto:

// Hypothetical usage example
const { isHovered, isPressed, isFocusVisible } = useSomeAccessibilityHook()

const classString = buttonWithRules({
  states: {
    disabled: isDisabled,
    hover: isHovered,
    pressed: isPressed,
    focusVisible: isFocusVisible,
  },
})

Arto merges them all and produces the final output. This approach scales well for any front-end setup.

Getting Started

Install with your preferred package manager:

pnpm add arto
# or npm install arto
# or yarn add arto

Then define an Arto config in your code. For more guides and deeper examples:

Conclusion

Arto aims to solve the advanced use cases around variants, states, and utility classes. It’s especially useful if:

  • You need a robust system for toggling or merging classes.
  • You want to handle ephemeral or accessibility-related states thoroughly.
  • You appreciate typed safety (TypeScript) and want everything in one place.

Ultimately, you can drop Arto into any framework or plain JavaScript project. The React examples are just that—examples.

I’d love for you to try it out, share feedback, and help me refine it. Feel free to open an issue on GitHub if you spot any bugs or have suggestion.

Author Of article : Hamid Elgendy Read full article