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
2 changes: 1 addition & 1 deletion .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
workflow_dispatch:
inputs:
release_tag:
description: "Release tag to publish, for example v2.0.0"
description: "Release tag to publish, for example v2.1.0"
required: true
type: string

Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

All notable changes to `layout-style-css` are documented here. This project follows semantic versioning.

## [2.1.0] - 2026-07-20

### Changed

- Updated the ecosystem demo and documentation to use `ui-style-kit-css@2.1.0` visual CSS, `ui-style-kit-css/interactive-surface-theme.css`, `interactive-surface-css@1.5.0` state core, and Layout core as the canonical import order.
- Kept `layout-style-css/integrations/ui-style-kit.css` available as a frozen deprecated structural bridge for legacy UI-prefixed layout aliases.
- Updated release metadata and the GitHub Pages demo to identify the 2.1.0 package line.

### Tests

- Switched demo fixtures to consume the staged UI Style Kit 2.1 manifest, visual bundle, and token bridge while locking Interactive Surface to the released 1.5.0 package.

## [2.0.0] - 2026-07-19

### Breaking
Expand Down
31 changes: 18 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@

Container-first, dependency-free structural CSS for responsive application layouts.

`layout-style-css@2.0.0` provides semantic wrappers, composition primitives, seven functional recipes, ordering escape hatches, and sixteen spatial personalities. It owns layout only; color, typography, borders, shadows, component paint, and interactive states belong to other libraries.
`layout-style-css@2.1.0` provides semantic wrappers, composition primitives, seven functional recipes, ordering escape hatches, sixteen spatial personalities, and refreshed ecosystem fixtures for UI Style Kit CSS 2.1 and Interactive Surface CSS 1.5. It owns layout only; color, typography, borders, shadows, component paint, and interactive states belong to other libraries.

## Requirements

- Node.js 20 or newer for development and package scripts
- Current evergreen Chromium, Firefox, or WebKit for consumers
- No runtime or peer dependencies

`ui-style-kit-css@2.0.1` and `interactive-surface-css@1.4.0` are exact development and integration fixtures. They are not installed for consumers. A separate UI Style Kit revision is a follow-up and is not part of this release.
`ui-style-kit-css@2.1.0` and `interactive-surface-css@1.5.0` are released registry fixtures for optional ecosystem verification. Neither package is installed for consumers.

## Install

```bash
npm install layout-style-css@2.0.0
npm install layout-style-css@2.1.0
```

Standalone use needs one import:
Expand All @@ -33,21 +33,20 @@ Layout Style never imports companion packages. Install and import each layer exp
Layout plus UI Style Kit:

```js
import "ui-style-kit-css/with-bridge.css";
import "layout-style-css/integrations/ui-style-kit.css";
import "ui-style-kit-css/visual.css";
import "layout-style-css";
```

Layout plus UI Style Kit and Interactive Surface must use this order:

```js
import "ui-style-kit-css/with-bridge.css";
import "ui-style-kit-css/visual.css";
import "ui-style-kit-css/interactive-surface-theme.css";
import "interactive-surface-css/state-core.css";
import "layout-style-css/integrations/ui-style-kit.css";
import "layout-style-css";
```

The UI Style Kit bridge establishes its visual tokens, Interactive Surface contributes interaction states, the Layout integration maps structural UI aliases, and Layout core applies spatial behavior.
UI Style Kit establishes visual paint, the UI token bridge maps theme roles for Interactive Surface, Interactive Surface contributes interaction states, and Layout core applies spatial behavior.

## Public Exports

Expand All @@ -62,7 +61,7 @@ The UI Style Kit bridge establishes its visual tokens, Interactive Surface contr
| `layout-style-css/utilities.css` | Structural utilities and ordering escape hatches. |
| `layout-style-css/personalities.css` | All sixteen personalities. |
| `layout-style-css/personalities/minimal-saas.css` | One personality; substitute any public personality name. |
| `layout-style-css/integrations/ui-style-kit.css` | Import-free structural bridge for UI Style Kit names. |
| `layout-style-css/integrations/ui-style-kit.css` | Deprecated import-free structural bridge for legacy UI Style Kit structural aliases. |
| `layout-style-css/legacy.css` | Full v2 bundle plus v1 compatibility aliases. |

The old root-level personality files and `all-with-ui-kit*` aggregates were removed. See [Migrating To 2.0](docs/wiki/Migrating-To-2.0.md) for exact mappings.
Expand Down Expand Up @@ -159,7 +158,13 @@ These are escape hatches. Visual reordering can diverge from screen-reader readi

## UI Style Kit Naming Compatibility

Import `layout-style-css/integrations/ui-style-kit.css` after the UI and interaction layers. The bridge maps structural aliases such as `.saas-container`, `.bento-grid`, and `.cyber-split` without importing packages or redefining visual tokens.
`layout-style-css/integrations/ui-style-kit.css` remains available for legacy code that still uses UI-prefixed structural aliases such as `.saas-container`, `.bento-grid`, and `.cyber-split`. It is frozen and deprecated; new ecosystem imports should use UI Style Kit visual builds plus Layout core instead of the structural bridge.

Deprecated bridge import:

```js
import "layout-style-css/integrations/ui-style-kit.css";
```

Supported UI prefixes are `saas`, `bento`, `max`, `bau`, `tactile`, `neo`, `retro`, `brutal`, `cyber`, `y2k`, and `rg`.

Expand All @@ -178,11 +183,11 @@ The v1 `.ly-surface--raised` selector is intentionally removed, including from `
## CDN

```html
<link rel="stylesheet" href="https://unpkg.com/layout-style-css@2.0.0/dist/layout-style-css.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/layout-style-css@2.0.0/dist/layout-style-css.min.css">
<link rel="stylesheet" href="https://unpkg.com/layout-style-css@2.1.0/dist/layout-style-css.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/layout-style-css@2.1.0/dist/layout-style-css.min.css">
```

Companion CDN styles must appear in the same order as the package imports: UI Style Kit bridge, Interactive Surface `state-core.css`, Layout integration bridge, then Layout core.
Companion CDN styles must appear in the same order as the package imports: UI Style Kit visual CSS, UI Style Kit `interactive-surface-theme.css`, Interactive Surface `state-core.css`, then Layout core. Load `layout-style-css/integrations/ui-style-kit.css` only for deprecated structural aliases.

## Development And Verification

Expand Down
171 changes: 128 additions & 43 deletions demo/demo.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,82 @@
const UI_STYLE_KIT_VERSION = "2.1.0";
const UI_STYLE_KIT_MANIFEST_URL = `https://unpkg.com/ui-style-kit-css@${UI_STYLE_KIT_VERSION}/manifest.json`;
const UI_STYLE_KIT_MANIFEST_FALLBACK = Object.freeze({
version: UI_STYLE_KIT_VERSION,
presets: Object.freeze([
Object.freeze({ id: "minimal-saas", label: "Minimal SaaS", prefix: "saas" }),
Object.freeze({ id: "bento", label: "Bento", prefix: "bento" }),
Object.freeze({ id: "maximalist", label: "Maximalist", prefix: "max" }),
Object.freeze({ id: "bauhaus", label: "Bauhaus", prefix: "bau" }),
Object.freeze({ id: "tactile", label: "Tactile", prefix: "tactile" }),
Object.freeze({ id: "neumorphism", label: "Neumorphism", prefix: "neo" }),
Object.freeze({ id: "retrofuturism", label: "Retrofuturism", prefix: "retro" }),
Object.freeze({ id: "brutalism", label: "Brutalism", prefix: "brutal" }),
Object.freeze({ id: "cyberpunk", label: "Cyberpunk", prefix: "cyber" }),
Object.freeze({ id: "y2k", label: "Y2K", prefix: "y2k" }),
Object.freeze({ id: "retro-glass", label: "Retro Glass", prefix: "rg" })
]),
themes: Object.freeze([
"midnight-gold",
"ocean-steel",
"forest-moss",
"sunset-ember",
"royal-plum",
"graphite-cyan",
"desert-sage",
"rose-quartz",
"cyber-lime",
"arctic-indigo"
]),
modes: Object.freeze(["light", "dark", "contrast"])
});

function normalizeUiStyleKitManifest(manifest) {
const normalized = {
version: String(manifest?.version ?? UI_STYLE_KIT_VERSION),
presets: Array.isArray(manifest?.presets) ? manifest.presets : [],
themes: Array.isArray(manifest?.themes) ? manifest.themes : [],
modes: Array.isArray(manifest?.modes) ? manifest.modes : []
};
const presets = normalized.presets
.map((preset) => ({
id: String(preset?.id ?? ""),
label: String(preset?.label ?? preset?.id ?? ""),
prefix: String(preset?.prefix ?? "")
}))
.filter((preset) => preset.id && preset.prefix);

if (presets.length === 0 || normalized.themes.length === 0 || normalized.modes.length === 0) {
throw new Error("UI Style Kit manifest is missing presets, themes, or modes.");
}

return Object.freeze({
version: normalized.version,
presets: Object.freeze(presets.map((preset) => Object.freeze(preset))),
themes: Object.freeze(normalized.themes.map(String)),
modes: Object.freeze(normalized.modes.map(String))
});
}

async function loadUiStyleKitManifest() {
try {
const response = await fetch(UI_STYLE_KIT_MANIFEST_URL, { cache: "force-cache" });

if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}

return normalizeUiStyleKitManifest(await response.json());
} catch (error) {
/*
The published demo can render before the companion UI release reaches every CDN edge.
The fallback mirrors the 2.1 manifest contract so layout behavior stays testable.
*/
console.warn("UI Style Kit 2.1 manifest unavailable; using the packaged fallback.", error);
return UI_STYLE_KIT_MANIFEST_FALLBACK;
}
}

const UI_STYLE_KIT_MANIFEST = await loadUiStyleKitManifest();
const ALLOWLISTS = Object.freeze({
wrapper: Object.freeze(["default", "compact", "prose", "content", "wide", "full", "breakout"]),
recipe: Object.freeze([
Expand Down Expand Up @@ -29,31 +108,9 @@ const ALLOWLISTS = Object.freeze({
]),
container: Object.freeze(["auto", "40rem", "47rem", "49rem", "63rem", "65rem", "80rem"]),
density: Object.freeze(["compact", "comfortable", "spacious"]),
ui: Object.freeze([
"minimal-saas",
"bento",
"maximalist",
"bauhaus",
"tactile",
"neumorphism",
"retrofuturism",
"brutalism",
"cyberpunk",
"y2k",
"retro-glass"
]),
theme: Object.freeze([
"arctic-indigo",
"ocean-steel",
"graphite-cyan",
"forest-moss",
"rose-quartz",
"desert-sage",
"midnight-gold",
"cyber-lime",
"sunset-ember"
]),
mode: Object.freeze(["light", "dark", "contrast"]),
ui: Object.freeze(UI_STYLE_KIT_MANIFEST.presets.map((preset) => preset.id)),
theme: Object.freeze(UI_STYLE_KIT_MANIFEST.themes),
mode: Object.freeze(UI_STYLE_KIT_MANIFEST.modes),
ecosystem: Object.freeze(["layout-only", "layout-ui", "all-three"])
});

Expand Down Expand Up @@ -85,19 +142,9 @@ const DENSITY_GAPS = Object.freeze({
spacious: "1.5rem"
});

const UI_CLASS_PREFIXES = Object.freeze({
"minimal-saas": "saas",
bento: "bento",
maximalist: "max",
bauhaus: "bau",
tactile: "tactile",
neumorphism: "neo",
retrofuturism: "retro",
brutalism: "brutal",
cyberpunk: "cyber",
y2k: "y2k",
"retro-glass": "rg"
});
const UI_CLASS_PREFIXES = Object.freeze(
Object.fromEntries(UI_STYLE_KIT_MANIFEST.presets.map((preset) => [preset.id, preset.prefix]))
);

const RECIPE_CLASSES = Object.freeze({
"app-shell": "ly-app-shell",
Expand All @@ -120,14 +167,13 @@ const RECIPE_AREAS = Object.freeze({
const ECOSYSTEM_IMPORTS = Object.freeze({
"layout-only": Object.freeze(['import "layout-style-css";']),
"layout-ui": Object.freeze([
'import "ui-style-kit-css/with-bridge.css";',
'import "layout-style-css/integrations/ui-style-kit.css";',
'import "ui-style-kit-css/visual.css";',
'import "layout-style-css";'
]),
"all-three": Object.freeze([
'import "ui-style-kit-css/with-bridge.css";',
'import "ui-style-kit-css/visual.css";',
'import "ui-style-kit-css/interactive-surface-theme.css";',
'import "interactive-surface-css/state-core.css";',
'import "layout-style-css/integrations/ui-style-kit.css";',
'import "layout-style-css";'
])
});
Expand All @@ -152,6 +198,7 @@ const copyStatus = document.querySelector("#copyStatus");
const ecosystemStatus = document.querySelector("#ecosystemStatus");
const containerReadout = document.querySelector("#containerReadout");
const uiKitStylesheet = document.querySelector("#uiKitStylesheet");
const uiKitInteractiveThemeStylesheet = document.querySelector("#uiKitInteractiveThemeStylesheet");
const interactiveSurfaceStylesheet = document.querySelector("#interactiveSurfaceStylesheet");
const layoutIntegrationStylesheet = document.querySelector("#layoutIntegrationStylesheet");
const layoutCoreStylesheet = document.querySelector("#layoutCoreStylesheet");
Expand All @@ -162,6 +209,9 @@ const drawerBackdrop = document.querySelector("#demoControlsBackdrop");
const stateToggle = document.querySelector("#stateToggle");
const mobileControlsQuery = window.matchMedia("(max-width: 63.999rem)");

body.dataset.uiManifestVersion = UI_STYLE_KIT_MANIFEST.version;
syncUiManifestSelectOptions();

let state = readStateFromQuery();
let drawerReturnFocus = null;
let querySyncTimer = null;
Expand Down Expand Up @@ -302,6 +352,40 @@ function formatLabel(value) {
.join(" ");
}

function syncUiManifestSelectOptions() {
const optionGroups = {
ui: UI_STYLE_KIT_MANIFEST.presets.map((preset) => ({
value: preset.id,
label: preset.label || formatLabel(preset.id)
})),
theme: UI_STYLE_KIT_MANIFEST.themes.map((theme) => ({
value: theme,
label: formatLabel(theme)
})),
mode: UI_STYLE_KIT_MANIFEST.modes.map((mode) => ({
value: mode,
label: mode === "contrast" ? "High contrast" : formatLabel(mode)
}))
};

for (const [key, options] of Object.entries(optionGroups)) {
const select = controls[key];

if (!select) {
continue;
}

select.replaceChildren(
...options.map(({ value, label }) => {
const option = document.createElement("option");
option.value = value;
option.textContent = label;
return option;
})
);
}
}

function syncUiKitClasses() {
const prefix = UI_CLASS_PREFIXES[state.ui];

Expand Down Expand Up @@ -330,8 +414,9 @@ function syncEcosystem() {
const includesInteractiveSurface = state.ecosystem === "all-three";

uiKitStylesheet.disabled = !includesUi;
uiKitInteractiveThemeStylesheet.disabled = !includesInteractiveSurface;
interactiveSurfaceStylesheet.disabled = !includesInteractiveSurface;
layoutIntegrationStylesheet.disabled = !includesUi;
layoutIntegrationStylesheet.disabled = true;
layoutCoreStylesheet.disabled = false;
ecosystemStatus.textContent = ECOSYSTEM_LABELS[state.ecosystem];
}
Expand Down
Loading
Loading