Tasty logoTasty
Playground

Getting Started

Build one stateful component first. You will see how Tasty expresses priority as a state map, why overlapping states do not compete, and where shared design-system configuration fits afterward.

This is the right starting point when you are ready to try Tasty in code. If you are still evaluating it, read the Introduction, Comparison, and Adoption Guide.


Prerequisites

No Tasty configuration is required for the first component.


Install

pnpm add @tenphi/tasty

Build a button whose states don’t fight

import { tasty } from '@tenphi/tasty';

const Button = tasty({
  as: 'button',
  styles: {
    padding: '12px 20px',
    radius: '8px',
    border: '0',
    fill: {
      '': 'royalblue',
      ':hover': 'blue',
      ':active': 'navy',
      disabled: 'lightgray',
    },
    color: {
      '': 'white',
      disabled: 'dimgray',
    },
    cursor: {
      '': 'pointer',
      disabled: 'not-allowed',
    },
  },
});

export default function App() {
  return (
    <>
      <Button>Save changes</Button>
      <Button disabled>Disabled</Button>
    </>
  );
}

tasty() returns a normal React component. The keys in each state map define priority: later branches win over earlier ones. Because disabled is last, a disabled button keeps its disabled styles even while the pointer is over it.

disabled and checked are built-in automatic states. Tasty activates them from the corresponding native prop or attribute, so disabled is the concise Tasty form of [disabled], and checked is the concise form of [checked].

Tasty compiles that priority into selectors that exclude one another. The hover, active, and disabled background rules cannot all match and ask the CSS cascade to decide the winner.

The example uses ordinary CSS values so it works without setup. A design system will usually replace them with shared tokens, units, and state aliases in the next step. See the Style DSL for the complete state-map syntax and Style Properties for Tasty’s enhanced CSS properties.


Add your design system’s language

The first component needs no configuration. Use configure() once, before your app renders, when your app or design system is ready to share tokens, state aliases, recipes, units, or parser extensions:

// src/tasty-config.ts
import { configure } from '@tenphi/tasty';

configure({
  states: {
    '@mobile': '@media(w < 768px)',
    '@dark': '@root(schema=dark)',
  },
});

These examples use data-schema="dark" as the root-state convention. If your app already uses a different attribute such as data-theme="dark", keep the pattern and swap the attribute name consistently across your config and components.

Import this file at the top of your app entry point so it runs before any component renders:

// src/main.tsx
import './tasty-config';
import { createRoot } from 'react-dom/client';
import App from './App';

createRoot(document.getElementById('root')!).render(<App />);

Define shared tokens and override default unit values

Color tokens like #primary resolve to CSS custom properties at runtime (e.g. var(--primary-color)). Built-in units like x, r, and bw already work without setup and multiply CSS custom properties by default. Use configure({ tokens }) when you want to define shared token values or override the defaults your app uses:

// src/tasty-config.ts
import { configure } from '@tenphi/tasty';

configure({
  tokens: {
    '#primary': 'oklch(55% 0.25 265)',
    '#surface': '#fff',
    '#text': '#111',
    $gap: '8px',
    $radius: '4px',
    '$border-width': '1px',
    '$outline-width': '2px',
  },
});

Tokens support state maps for responsive or theme-aware values:

configure({
  tokens: {
    '#primary': {
      '': 'oklch(55% 0.25 265)',
      '@dark': 'oklch(75% 0.2 265)',
    },
  },
});

Every component using #primary, 2x, or 1r adjusts automatically. Tokens are injected as :root CSS custom properties when the first style is rendered. You can also use standard CSS color values such as rgb(...), hsl(...), and named colors directly; okhsl(...) is the recommended choice when you want authored colors that stay aligned with Tasty's design-system-oriented workflow.

Note: configure({ replaceTokens }) is a separate mechanism — it replaces tokens with literal values at parse time (baked into CSS). Use it for value aliases like $card-padding: '4x' that should be resolved during style generation, not for defining color or unit values. See Configuration — Replace Tokens for details.

See Configuration for the full configure() API — tokens, replace tokens, recipes, custom units, style handlers, and TypeScript extensions.


ESLint plugin

The ESLint plugin catches invalid style properties, bad token references, malformed state keys, and other mistakes at lint time — before they reach the browser.

Install

pnpm add -D @tenphi/eslint-plugin-tasty

Configure

Add the plugin to your flat config:

// eslint.config.js
import tasty from '@tenphi/eslint-plugin-tasty';

export default [
  // ...your other configs
  tasty.configs.recommended,
];

The recommended config covers common correctness and style issues:

CategoryRulesExamples
Property validationknown-property, valid-boolean-property, valid-sub-elementFlags typos like pading or invalid boolean usage
Value validationvalid-value, valid-color-token, valid-custom-unitCatches #nonexistent tokens, bad unit syntax
State validationvalid-state-key, no-nested-state-map, require-default-stateValidates state key syntax, ensures '' default exists
Structurevalid-styles-structure, no-important, no-nested-selectorPrevents !important, invalid nesting
Static modestatic-no-dynamic-values, static-valid-selectorEnforces build-time constraints in tastyStatic()
Style propertiesvalid-preset, valid-recipe, valid-transition, valid-directional-modifier, valid-radius-shapeValidates preset names, recipe references, transition syntax
Motion guidanceno-raw-transition-durationSuggests timing tokens or the default timing for transition durations
Dynamic stylesno-runtime-styles-mutation, no-style-spreadWarns on JavaScript values and spreads inside runtime style objects

Strict config

For stricter governance, use tasty.configs.strict. It adds rules that flag direct styles prop usage and other patterns:

export default [tasty.configs.strict];

Editor support

VS Code Extension — Syntax highlighting for Tasty styles in TypeScript/TSX/JavaScript/JSX. Highlights color tokens, custom units, state keys, presets, and style properties inside tasty() and tastyStatic() calls. Install from the VS Code marketplace or from a .vsix file.

Glaze — OKHSL-based color theme generator with automatic WCAG contrast solving. Generate light, dark, and high-contrast color palettes from a single hue and export them directly as Tasty color tokens. See the Ecosystem section in the README.


Choosing a rendering mode

tasty() is the default for all React apps. All tasty() components and style functions are hook-free and work as React Server Components without 'use client'. Zero-runtime delivery is not tied to one API: both server-only tasty() and build-time tastyStatic() can ship no Tasty styling runtime to the browser.

ApproachAuthoring APICSS is generatedTasty runtime in the browserBest for
Server-only Reacttasty(); optional @tenphi/tasty/ssr/*During server or static renderingNoneAstro without islands, server-only RSC, SSG
Hydrated Reacttasty() plus @tenphi/tasty/ssr/*During SSR, then on demand in ReactOnly in hydrated componentsInteractive React apps, Next.js client components, Astro islands
Build-time extractiontastyStatic() from @tenphi/tasty/staticDuring the buildNone in file modeNon-React frameworks or extraction before rendering

Both tasty() and tastyStatic() share the same DSL, tokens, units, and state mappings.


Next steps


Common issues