CSS Hooks
Documentation
Source on GitHub

Quickstart: No framework

1 Initialize project

npm create vite@latest css-hooks-playground -- --template vanilla-ts cd css-hooks-playground npm install @css-hooks/core remeda

2 Start dev server

npm run dev

Visit http://localhost:5173 to view changes in real time.

3 Set up CSS Hooks

Create a src/css.ts module with the following contents:

import { buildHooksSystem } from "@css-hooks/core"; const createHooks = buildHooksSystem(); export const { styleSheet, on } = createHooks("&:active"); /** * Converts a style object to a string. * * @remarks * This functionality (or equivalent) would typically be bundled with an app framework. */ export function styleObjectToString(obj: Record<string, unknown>) { return Object.entries(obj) .filter( ([, value]) => typeof value === "string" || typeof value === "number", ) .map( ([property, value]) => `${/^--/.test(property) ? property : property.replace(/[A-Z]/g, x => `-${x.toLowerCase()}`)}: ${value}`, ) .join("; "); }

4 Add style sheet

Modify src/main.ts to add the style sheet to the document:

import './style.css' import typescriptLogo from './typescript.svg' import viteLogo from '/vite.svg' import { setupCounter } from './counter.ts' import { styleSheet } from './css.ts' document.querySelector<HTMLDivElement>('#app')!.innerHTML = ` <style>${styleSheet()}</style> <div> <a href="https://vitejs.dev" target="_blank"> <img src="${viteLogo}" class="logo" alt="Vite logo" /> </a> <a href="https://www.typescriptlang.org/" target="_blank"> <img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" /> </a> <h1>Vite + TypeScript</h1> <div class="card"> <button id="counter" type="button"></button> </div> <p class="read-the-docs"> Click on the Vite and TypeScript logos to learn more </p> </div> ` setupCounter(document.querySelector<HTMLButtonElement>('#counter')!)

5 Add conditional style

Use the configured &:active hook to implement an effect when the counter button is pressed:

// src/main.ts import './style.css' import typescriptLogo from './typescript.svg' import viteLogo from '/vite.svg' import { setupCounter } from './counter.ts' import { styleSheet } from './css.ts' import { on, styleObjectToString, styleSheet } from './css.ts' import { pipe } from 'remeda' document.querySelector<HTMLDivElement>('#app')!.innerHTML = ` <style>${styleSheet()}</style> <div> <a href="https://vitejs.dev" target="_blank"> <img src="${viteLogo}" class="logo" alt="Vite logo" /> </a> <a href="https://www.typescriptlang.org/" target="_blank"> <img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" /> </a> <h1>Vite + TypeScript</h1> <div class="card"> <button id="counter" type="button"></button> <button id="counter" type="button" style="${styleObjectToString( pipe( { transition: 'transform 75ms', }, on('&:active', { transform: 'scale(0.9)' }) ) )}"> </button> </div> <p class="read-the-docs"> Click on the Vite and TypeScript logos to learn more </p> </div> ` setupCounter(document.querySelector<HTMLButtonElement>('#counter')!)