From 239ad7459e22121a5a899dce42a3a8850ed74f31 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 11 Mar 2026 13:15:38 -0700 Subject: [PATCH] Add global css() function for element/reset styles --- src/index.tsx | 13 +++++++ src/tests/index.test.tsx | 77 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/index.tsx b/src/index.tsx index 08a711b..820aeb5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -318,5 +318,18 @@ function tagName(base: string): string { return capitalized === 'A' ? 'Anchor' : capitalized } +// global CSS for resets, element defaults, pseudo-elements +export function css(rules: Record) { + for (const [selector, def] of Object.entries(rules)) { + styles[selector] = makeStyle(def) + + for (const [state, stateDef] of Object.entries(def.states ?? {})) + styles[`${selector}${stateName(state)}`] = makeStyle(stateDef) + } + + injectStylesInBrowser() +} + // shortcut so you only have to import one thing, if you want define.Styles = Styles +define.css = css diff --git a/src/tests/index.test.tsx b/src/tests/index.test.tsx index c1f3a03..bff980b 100644 --- a/src/tests/index.test.tsx +++ b/src/tests/index.test.tsx @@ -1,5 +1,5 @@ import { describe, test, expect } from 'bun:test' -import { define } from '../index' +import { define, css } from '../index' import { renderToString, getStylesCSS, parseCSS } from './test_helpers' describe('define - basic functionality', () => { @@ -1057,3 +1057,78 @@ describe('edge cases', () => { expect(html).toContain('Second') }) }) + +describe('css() - global styles', () => { + test('registers bare element selectors', () => { + css({ + 'body': { margin: 0, fontFamily: 'system-ui' } + }) + + const out = getStylesCSS() + expect(out).toContain('body {') + expect(out).toContain('font-family: system-ui') + expect(out).toContain('margin: 0px') + }) + + test('registers universal selector', () => { + css({ + '*': { boxSizing: 'border-box' } + }) + + const out = getStylesCSS() + expect(out).toContain('* {') + expect(out).toContain('box-sizing: border-box') + }) + + test('registers compound selectors', () => { + css({ + '*, *::before, *::after': { boxSizing: 'border-box' } + }) + + const out = getStylesCSS() + expect(out).toContain('*, *::before, *::after {') + }) + + test('registers pseudo-element selectors', () => { + css({ + '::-webkit-scrollbar': { width: 8 } + }) + + const out = getStylesCSS() + expect(out).toContain('::-webkit-scrollbar {') + expect(out).toContain('width: 8px') + }) + + test('handles states on global selectors', () => { + css({ + 'a': { + color: 'blue', + states: { + hover: { color: 'darkblue' } + } + } + }) + + const out = getStylesCSS() + expect(out).toContain('a {') + expect(out).toContain('a:hover {') + expect(out).toContain('color: darkblue') + }) + + test('registers multiple selectors at once', () => { + css({ + 'html': { lineHeight: 1.5 }, + 'h1': { fontSize: 32 }, + 'p': { margin: 0 } + }) + + const out = getStylesCSS() + expect(out).toContain('html {') + expect(out).toContain('h1 {') + expect(out).toContain('p {') + }) + + test('accessible via define.css', () => { + expect(define.css).toBe(css) + }) +})