Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,11 @@ pnpm add @eqtylab/equality

```css
@import '@eqtylab/equality/theme-config.css';
@import '@eqtylab/equality/preflight.css' layer(base);
```

See the [Usage guide](https://equality.eqtylab.io/getting-started/usage) for the available stylesheets and how to embed Equality in a site that has its own base styles.

Or

```ts
Expand Down Expand Up @@ -159,4 +162,4 @@ Requirements:
## Troubleshooting

- If the demo doesn’t start, ensure Node ≥ 18 and pnpm ≥ 9.
- If styles don't appear, confirm the CSS import: `@import '@eqtylab/equality/theme-config.css'`.
- If styles don't appear, confirm the CSS imports listed in the [Usage guide](https://equality.eqtylab.io/getting-started/usage).
14 changes: 12 additions & 2 deletions packages/demo/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ const uiSrc = resolve(__dirname, "../ui/src");

// Resolve @eqty/equality to local ui package when viewing demo
const PKG = "@eqtylab/equality";
// Published CSS subpaths whose specifier doesn't match its path under src/.
/** @type {Record<string, string | undefined>} */
const CSS_SUBPATHS = {
"theme-config.css": "theme/global-theme-config.css",
"preflight.css": "theme/theme-preflight-global.css",
"preflight-scoped.css": "theme/theme-preflight-scoped.css",
};
const resolveUiFromSource = {
name: "resolve-ui-from-source",
enforce: /** @type {const} */ ("pre"),
Expand All @@ -29,8 +36,11 @@ const resolveUiFromSource = {
async resolveId(id, importer, options) {
/** @type {string | undefined} */
let target;
if (id === `${PKG}/theme-config.css`) {
target = resolve(uiSrc, "theme/global-theme-config.css");
const cssSubpath = id.startsWith(`${PKG}/`)
? CSS_SUBPATHS[id.slice(PKG.length + 1)]
: undefined;
if (cssSubpath) {
target = resolve(uiSrc, cssSubpath);
} else if (id === PKG) {
target = uiSrc; // directory -> index.ts
} else if (id.startsWith(`${PKG}/`)) {
Expand Down
39 changes: 38 additions & 1 deletion packages/demo/src/pages/getting-started/usage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,47 @@ Please follow either of the setup configurations below depending on your project

### Tailwind v4

Import the Equality Tailwind v4 configuration into your global stylesheet (this includes preflight (base styles), theme colors, variables, utilities and components):
The package ships three stylesheets:

| Stylesheet | Contains |
| ---------------------------------------- | ----------------------------------------------------- |
| `@eqtylab/equality/theme-config.css` | Tokens, theme, utilities and components. No reset. |
| `@eqtylab/equality/preflight.css` | Base styles reset for `html` and everything under it. |
| `@eqtylab/equality/preflight-scoped.css` | The same reset, only inside `[data-equality-root]`. |

In an app you own, import the config followed by the global preflight:

```css
@import "@eqtylab/equality/theme-config.css";
@import "@eqtylab/equality/preflight.css" layer(base);
```

`theme-config.css` must come first — it declares the `theme, base, components, utilities` layer order that the preflight relies on.

### Embedding in a host site

The preflight is a document-wide reset, so it will flatten the typography of a site that ships its own base styles, such as a Starlight or Zensical documentation site. Use the scoped preflight there instead:

```css
@import "@eqtylab/equality/theme-config.css";
@import "@eqtylab/equality/preflight-scoped.css" layer(base);
```

`<ThemeProvider />` sets `data-equality-root` for you. You can also mark a plain wrapper, as many times per page as you need:

```html
<div data-equality-root>...</div>
```

Neither preflight declares a layer of its own, so `layer()` can place the reset wherever the host's cascade needs it — for example `layer(equality-reset)` ordered against Starlight's own layer.

Note that Equality's dark palette activates on `html[data-equality-theme="dark"]`, which a host's theme toggle won't set. Mirror the host's attribute to follow its switcher:

```js
const html = document.documentElement;
new MutationObserver(() => {
html.dataset.equalityTheme = html.dataset.theme;
}).observe(html, { attributes: true, attributeFilter: ["data-theme"] });
```

### Other CSS frameworks
Expand Down
1 change: 1 addition & 0 deletions packages/demo/src/styles/global.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@import "tailwindcss";

@import "@eqtylab/equality/theme-config.css";
@import "@eqtylab/equality/preflight.css" layer(base);

@layer components {
body {
Expand Down
6 changes: 6 additions & 0 deletions packages/demo/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
"@eqtylab/equality/theme-config.css": [
"../ui/src/theme/global-theme-config.css"
],
"@eqtylab/equality/preflight.css": [
"../ui/src/theme/theme-preflight-global.css"
],
"@eqtylab/equality/preflight-scoped.css": [
"../ui/src/theme/theme-preflight-scoped.css"
],
"@eqtylab/equality/*": ["../ui/src/*"],
"@/": ["../ui/src"],
"@/*": ["../ui/src/*"]
Expand Down
9 changes: 6 additions & 3 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@eqtylab/equality",
"description": "EQTYLab's component and token-based design system",
"homepage": "https://equality.eqtylab.io/",
"version": "2.5.0",
"version": "3.0.0",
"license": "Apache-2.0",
"keywords": [
"component library",
Expand All @@ -25,7 +25,9 @@
"import": "./dist/scripts.js",
"require": "./dist/scripts.cjs"
},
"./theme-config.css": "./dist/global-theme-config.css"
"./theme-config.css": "./dist/global-theme-config.css",
"./preflight.css": "./dist/theme-preflight-global.css",
"./preflight-scoped.css": "./dist/theme-preflight-scoped.css"
},
"files": [
"dist",
Expand Down Expand Up @@ -93,7 +95,8 @@
"dev": "pnpm run watch:tokens",
"build": "pnpm run build:tokens && pnpm run build:lib",
"build:tokens": "node src/scripts/build-tokens.js && prettier --write ../tokens/equality-tokens.json",
"build:lib": "tsup --config tsup.config.ts && cp src/theme/*.css dist/ && mkdir -p dist/generated && cp src/theme/generated/*.css dist/generated/",
"build:preflight": "node src/scripts/build-preflight.js && prettier --write src/theme/theme-preflight-global.css",
"build:lib": "pnpm run build:preflight && tsup --config tsup.config.ts && cp src/theme/*.css dist/ && mkdir -p dist/generated && cp src/theme/generated/*.css dist/generated/",
"watch": "tsup --config tsup.config.ts --watch",
"watch:tokens": "chokidar '../tokens/equality-tokens.json' --debounce 300 --initial=false -c 'pnpm run build:tokens'",
"release": "pnpm run build && pnpm publish --access public --no-git-checks"
Expand Down
48 changes: 48 additions & 0 deletions packages/ui/src/scripts/build-preflight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Generates the global preflight from the scoped one.
*
* `theme-preflight-scoped.css` is the single authored copy of the reset. The
* only difference in the global build is the root selector, so deriving it here
* keeps the two from drifting.
*/
import { readFileSync, writeFileSync } from 'fs';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));

const SOURCE = resolve(__dirname, '../theme/theme-preflight-scoped.css');
const OUTPUT = resolve(__dirname, '../theme/theme-preflight-global.css');

const SCOPED_ROOT = '[data-equality-root]';
const GLOBAL_ROOT = 'html';

const BANNER = `/* =========================================================
GLOBAL PREFLIGHT — GENERATED FILE, DO NOT EDIT

Generated from \`theme-preflight-scoped.css\` by
\`src/scripts/build-preflight.js\`. Edit that file instead, then run
\`pnpm build:preflight\`.

Deliberately carries no \`@layer\` wrapper: the importer assigns the layer,
so a consumer can place the reset wherever their cascade needs it.

@import '@eqtylab/equality/preflight.css' layer(base);
========================================================= */`;

const source = readFileSync(SOURCE, 'utf8');

// Drop the source's own header comment; the generated file gets its own.
const body = source.replace(/^\/\*[\s\S]*?\*\/\n/, '');

if (!body.includes(SCOPED_ROOT)) {
throw new Error(
`Expected to find "${SCOPED_ROOT}" in ${SOURCE}. Did the scoped root selector change?`
);
}

const output = `${BANNER}\n\n${body.split(SCOPED_ROOT).join(GLOBAL_ROOT)}`;

writeFileSync(OUTPUT, output);

console.log(`Generated ${OUTPUT} from ${SOURCE}`);
10 changes: 9 additions & 1 deletion packages/ui/src/theme/global-theme-config.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
@import './generated/tailwind-tokens.css';

@import './theme-base.css';
@import './theme-preflight-global.css';
@import './theme-components.css';
@import './theme-utilities.css';

/* NOTE: preflight is deliberately NOT imported here. It is an opt-in import so
that host sites with their own base styles can skip it or scope it:

@import '@eqtylab/equality/preflight.css' layer(base); // own the document
@import '@eqtylab/equality/preflight-scoped.css' layer(base); // reset only inside
// [data-equality-root]
Import this file first so the layer order above is registered before the
preflight lands in `base`. */
Loading
Loading