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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ All notable changes to this package are documented in this file.

## Unreleased

## 1.3.0 - 2026-07-08

### Added

- Added first-class `data-surface-variant` support for `primary`, `secondary`, `accent`, `subtle`, `warning`, and `danger` so runtime-assigned surface hooks match the existing `.variant-*` class API.
- Added standalone `data-surface-level="1"`, `"2"`, and `"3"` depth defaults with matching hover, active, and focus state-opacity hooks.
- Added Playwright coverage for data-variant parity and data-level depth defaults.
Comment thread
Foscat marked this conversation as resolved.

### Changed

- Updated README, wiki, and demo guidance to document standalone interaction hooks, UI Style Kit 2.x bridge usage, and the ecosystem boundary between layout, visual paint, and interaction states.
- Updated embedded demo docs to avoid stale version-pinned CDN examples.

## 1.2.4 - 2026-06-22

### Added
Expand Down
30 changes: 26 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ CDN:
<button class="interactive-surface size-lg variant-primary">Continue</button>
```

```html
<button class="interactive-surface" data-surface-variant="primary" data-surface-level="2">
Continue
</button>
```

```html
<button class="interactive-surface icon-only" aria-label="Settings">
<svg aria-hidden="true" viewBox="0 0 24 24">...</svg>
Expand Down Expand Up @@ -144,6 +150,13 @@ Visual variants:
- `.variant-warning`
- `.variant-danger`

Data attribute aliases:

- `data-surface-variant="primary|secondary|accent|subtle|warning|danger"`
- `data-surface-level="1|2|3"`

Use the class API when you prefer compact standalone markup. Use the data attributes when another library or runtime assigns semantic surface hooks.

Icon pattern:

- `.icon-only`
Expand All @@ -156,13 +169,23 @@ Preferred token namespace:

The package also supports legacy fallback tokens and semantic fallback tokens. Full details and examples are in [Token Reference](./wiki/Token-Reference.md).

## Ecosystem Integration

The three CSS libraries have separate ownership boundaries:

- `layout-style-css` owns structural layout recipes and page geometry.
- `ui-style-kit-css` owns visual themes, style systems, and mode-aware paint.
- `interactive-surface-css` owns interaction behavior on `.interactive-surface`.

Use one, two, or all three packages based on the layer you need. Interactive Surface does not require either companion library.

## Integration with UI Style Kit CSS

`interactive-surface-css` and `ui-style-kit-css` are designed to work separately and complementarily.

UI Style Kit owns visual theme tokens. Interactive Surface owns interaction behavior on `.interactive-surface`.

With `ui-style-kit-css@2.0.1`, the default full bundle does not include the bridge. Use the opt-in bridge bundle when you want UI Style Kit's runtime style switcher and Interactive Surface token mapping in one import:
With `ui-style-kit-css` 2.x, the default full bundle does not include the bridge. Use the opt-in bridge bundle when you want UI Style Kit's runtime style switcher and Interactive Surface token mapping in one import:

```js
import "ui-style-kit-css/with-bridge.css";
Expand All @@ -185,7 +208,7 @@ import "ui-style-kit-css/styles/minimal-saas.css";
import "ui-style-kit-css/interactive-surface-bridge";
```

The bridge maps active `data-ui`, `data-theme`, and `data-mode` tokens to Interactive Surface's public token contract, including variant, icon-role, state-layer, and optional surface-level hooks. UI Style Kit owns the bridge token mapping; Interactive Surface keeps ownership of `.interactive-surface` interaction behavior.
The bridge maps active `data-ui`, `data-theme`, and `data-mode` tokens to Interactive Surface's public token contract, including variant, icon-role, state-layer, and optional surface-level hooks. Interactive Surface now also treats `data-surface-variant` and `data-surface-level` as standalone public hooks, so bridge-generated markup keeps useful behavior even without style-specific class names. UI Style Kit owns the bridge token mapping; Interactive Surface keeps ownership of `.interactive-surface` interaction behavior.

## Accessibility

Expand Down Expand Up @@ -221,7 +244,7 @@ Release checklist:
2. Bump `version` in `package.json`.
3. Update `CHANGELOG.md`.
4. Push to `main`.
5. Create and publish a GitHub Release tag (for example `v1.2.4`).
5. Create and publish a GitHub Release tag (for example `v1.3.0`).
6. Verify the `Publish to npm` workflow succeeds.
7. Verify CDN availability:
- `https://cdn.jsdelivr.net/npm/interactive-surface-css@<version>/interactive-surface.css`
Expand All @@ -241,4 +264,3 @@ The `prepublishOnly` guard intentionally avoids browser downloads so `npm publis
`interactive-surface` should be the only transform-based motion owner on its host element.

Avoid applying additional `transform`, `translate`, `scale`, or `rotate` rules to the same node. If you need extra animation, apply it to a child element.
](https://github.com/Foscat/Interactive-Surface-CSS)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Interactive Surface Data Hooks Follow-Up

## Summary

This follow-up implements the `interactive-surface-css` portion of the three-library ecosystem plan. The package remains an interaction primitive, while adding data-attribute hooks that can be assigned by apps, renderers, or companion CSS libraries without requiring style-specific class names.

## Public API

- Existing class variants remain supported:
- `.variant-primary`
- `.variant-secondary`
- `.variant-accent`
- `.variant-subtle`
- `.variant-warning`
- `.variant-danger`
- New equivalent data variant hook:
- `data-surface-variant="primary|secondary|accent|subtle|warning|danger"`
- Existing `data-surface-level` support now has standalone defaults for:
- `data-surface-level="1"`
- `data-surface-level="2"`
- `data-surface-level="3"`

## Ownership Boundaries

- `layout-style-css` owns structure and layout recipes.
- `ui-style-kit-css` owns visual theme identity, color roles, and style systems.
- `interactive-surface-css` owns hover, focus, active, selected, pressed, disabled, reduced-motion, and forced-colors behavior on `.interactive-surface`.

Interactive Surface does not define global theme tokens and does not depend on either companion package.

## Verification Targets

- Data variant styles match the existing class variant styles.
- Surface levels have distinct standalone depth defaults.
- UI Style Kit bridge imports can continue to provide stronger token values through the public `--interactive-surface-*` contract.
- Existing accessibility behavior remains unchanged for focus, pressed, disabled, reduced motion, high contrast, and forced colors.
63 changes: 52 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,9 @@ <h2>Buttons and states</h2>
<div class="button-grid">
<button class="interactive-surface surface-btn variant-primary">Primary CTA</button>
<button class="interactive-surface surface-btn variant-secondary">Secondary</button>
<button class="interactive-surface surface-btn" data-surface-variant="primary" data-surface-level="2">
Data hook CTA
</button>
<button class="interactive-surface surface-btn variant-accent is-active">Selected (.is-active)</button>
<button class="interactive-surface surface-btn variant-warning">Warning</button>
<button class="interactive-surface surface-btn variant-danger">Danger</button>
Expand All @@ -1190,7 +1193,7 @@ <h2>Card surfaces</h2>
<strong>ARIA-aware state</strong>
<p>Pressed style can come from semantic attributes instead of custom state classes.</p>
</button>
<button class="interactive-surface surface-card variant-secondary">
<button class="interactive-surface surface-card variant-secondary" data-surface-level="3">
<strong>Content-rich cards</strong>
<p>Compose text, metadata, and icons while preserving a single interaction model.</p>
</button>
Expand Down Expand Up @@ -1338,9 +1341,9 @@ <h2>README and wiki links</h2>
</div>
</section>
</main>
<div id="tokenModal" class="token-modal" hidden>
<div id="tokenModal" class="token-modal" hidden aria-hidden="true">
<button id="tokenModalBackdrop" type="button" class="token-modal-backdrop" aria-label="Close token editor"></button>
<div class="token-modal-panel" role="dialog" aria-modal="true" aria-labelledby="tokenEditorTitle">
<div class="token-modal-panel">
<form id="tokenEditor" class="token-editor">
<div class="token-editor-head">
<strong id="tokenEditorTitle">Edit token</strong>
Expand Down Expand Up @@ -1447,11 +1450,11 @@ <h2>README and wiki links</h2>
import "interactive-surface-css";
```

CDN:
CDN (replace `<version>` with the desired release, e.g. `1.3.0`):

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/interactive-surface-css@1.2.0/interactive-surface.css" />
<link rel="stylesheet" href="https://unpkg.com/interactive-surface-css@1.2.0/interactive-surface.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/interactive-surface-css@<version>/interactive-surface.css" />
<link rel="stylesheet" href="https://unpkg.com/interactive-surface-css@<version>/interactive-surface.css" />
```

## Quick Start
Expand All @@ -1464,6 +1467,12 @@ <h2>README and wiki links</h2>
<button class="interactive-surface size-lg variant-primary">Continue</button>
```

```html
<button class="interactive-surface" data-surface-variant="primary" data-surface-level="2">
Continue
</button>
```

```html
<button class="interactive-surface icon-only" aria-label="Settings">
<svg aria-hidden="true" viewBox="0 0 24 24">...</svg>
Expand Down Expand Up @@ -1504,6 +1513,13 @@ <h2>README and wiki links</h2>
- `.variant-warning`
- `.variant-danger`

Data attribute aliases:

- `data-surface-variant="primary|secondary|accent|subtle|warning|danger"`
- `data-surface-level="1|2|3"`

Use the class API for compact standalone markup and the data attributes when another library or runtime assigns semantic surface hooks.

Icon pattern:

- `.icon-only`
Expand Down Expand Up @@ -1548,11 +1564,11 @@ <h2>README and wiki links</h2>
2. Bump `version` in `package.json`.
3. Update `CHANGELOG.md`.
4. Push to `main`.
5. Create and publish a GitHub Release tag (for example `v1.2.0`).
5. Create and publish a GitHub Release tag (for example `v1.3.0`).
6. Verify the `Publish to npm` workflow succeeds.
7. Verify CDN availability:
- `https://cdn.jsdelivr.net/npm/interactive-surface-css@1.2.0/interactive-surface.css`
- `https://unpkg.com/interactive-surface-css@1.2.0/interactive-surface.css`
- `https://cdn.jsdelivr.net/npm/interactive-surface-css@<version>/interactive-surface.css`
- `https://unpkg.com/interactive-surface-css@<version>/interactive-surface.css`

Manual fallback:

Expand Down Expand Up @@ -1589,6 +1605,7 @@ <h2>README and wiki links</h2>
const tokenCards = Array.from(document.querySelectorAll(".token-card[data-token]"));
const tokenGrid = document.querySelector(".token-grid");
const tokenModal = document.getElementById("tokenModal");
const tokenModalPanel = tokenModal?.querySelector(".token-modal-panel");
const tokenModalBackdrop = document.getElementById("tokenModalBackdrop");
const tokenEditor = document.getElementById("tokenEditor");
const tokenEditorTitle = document.getElementById("tokenEditorTitle");
Expand Down Expand Up @@ -1716,6 +1733,30 @@ <h2>README and wiki links</h2>
userTokenOverrides.textContent = blocks.join("\n\n");
}

/*
* Dialog semantics are applied only while visible so hidden demo markup
* is not exposed as an active modal to assistive technology or tests.
*/
function setTokenEditorModalState(isOpen) {
if (!tokenModal || !tokenModalPanel) {
return;
}

tokenModal.hidden = !isOpen;
tokenModal.setAttribute("aria-hidden", String(!isOpen));

if (isOpen) {
tokenModalPanel.setAttribute("role", "dialog");
tokenModalPanel.setAttribute("aria-modal", "true");
tokenModalPanel.setAttribute("aria-labelledby", "tokenEditorTitle");
return;
}

tokenModalPanel.removeAttribute("role");
tokenModalPanel.removeAttribute("aria-modal");
tokenModalPanel.removeAttribute("aria-labelledby");
}

function openTokenEditor(tokenName) {
if (!tokenEditor || !tokenEditorName || !tokenEditorValue || !tokenModal) {
return;
Expand All @@ -1724,7 +1765,7 @@ <h2>README and wiki links</h2>
tokenEditor.dataset.token = tokenName;
tokenEditorName.value = tokenName;
tokenEditorValue.value = readCssVariable(tokenName);
tokenModal.hidden = false;
setTokenEditorModalState(true);

if (tokenEditorTitle) {
tokenEditorTitle.textContent = `Edit ${tokenName}`;
Expand All @@ -1739,7 +1780,7 @@ <h2>README and wiki links</h2>
return;
}

tokenModal.hidden = true;
setTokenEditorModalState(false);
tokenEditor.dataset.token = "";
}

Expand Down
54 changes: 47 additions & 7 deletions interactive-surface.css
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,48 @@

/*
* UI Style Kit 2.x exposes surface depth through bridge level tokens.
* The attribute gate keeps default surfaces on the existing base contract.
* The attribute gates keep default surfaces on the existing base contract while
* letting bridge or standalone markup opt into semantic depth.
*/
.interactive-surface[data-surface-level] {
--_is-bg: var(--interactive-surface-level-bg, var(--_is-base-bg));
--_is-border-color: var(--interactive-surface-level-border-color, var(--_is-base-border-color));
--_is-shadow-base: var(--interactive-surface-level-shadow, var(--_is-base-shadow));
}

.interactive-surface[data-surface-level="1"] {
--_is-bg: var(--interactive-surface-level-bg, var(--interactive-surface-level-1-bg, var(--_is-base-bg)));
--_is-border-color: var(--interactive-surface-level-border-color, var(--interactive-surface-level-1-border-color, var(--_is-base-border-color)));
--_is-shadow-base: var(--interactive-surface-level-shadow, var(--interactive-surface-level-1-shadow, var(--_is-base-shadow)));
--_is-state-layer-hover-opacity: var(--interactive-surface-state-layer-hover-opacity, var(--interactive-surface-level-1-hover-opacity, 0.08));
--_is-state-layer-active-opacity: var(--interactive-surface-state-layer-active-opacity, var(--interactive-surface-level-1-active-opacity, 0.14));
--_is-state-layer-focus-opacity: var(--interactive-surface-state-layer-focus-opacity, var(--interactive-surface-level-1-focus-opacity, 0.18));
}

.interactive-surface[data-surface-level="2"] {
--_is-bg: var(--interactive-surface-level-bg, var(--interactive-surface-level-2-bg, var(--_is-base-bg)));
--_is-border-color: var(--interactive-surface-level-border-color, var(--interactive-surface-level-2-border-color, var(--_is-base-border-color)));
--_is-shadow-base: var(--interactive-surface-level-shadow, var(
--interactive-surface-level-2-shadow,
0 8px 20px rgb(0 0 0 / 0.22)
));
--_is-state-layer-hover-opacity: var(--interactive-surface-state-layer-hover-opacity, var(--interactive-surface-level-2-hover-opacity, 0.11));
--_is-state-layer-active-opacity: var(--interactive-surface-state-layer-active-opacity, var(--interactive-surface-level-2-active-opacity, 0.18));
--_is-state-layer-focus-opacity: var(--interactive-surface-state-layer-focus-opacity, var(--interactive-surface-level-2-focus-opacity, 0.22));
}

.interactive-surface[data-surface-level="3"] {
--_is-bg: var(--interactive-surface-level-bg, var(--interactive-surface-level-3-bg, var(--_is-base-bg)));
--_is-border-color: var(--interactive-surface-level-border-color, var(--interactive-surface-level-3-border-color, var(--_is-base-border-color)));
--_is-shadow-base: var(--interactive-surface-level-shadow, var(
--interactive-surface-level-3-shadow,
0 14px 34px rgb(0 0 0 / 0.3)
));
--_is-state-layer-hover-opacity: var(--interactive-surface-state-layer-hover-opacity, var(--interactive-surface-level-3-hover-opacity, 0.14));
--_is-state-layer-active-opacity: var(--interactive-surface-state-layer-active-opacity, var(--interactive-surface-level-3-active-opacity, 0.24));
--_is-state-layer-focus-opacity: var(--interactive-surface-state-layer-focus-opacity, var(--interactive-surface-level-3-focus-opacity, 0.28));
}

/* 2a) State-layer pseudo-element */

.interactive-surface::before {
Expand Down Expand Up @@ -324,7 +358,8 @@

/* 8) Visual variant token modifiers */

.interactive-surface.variant-primary {
.interactive-surface.variant-primary,
.interactive-surface[data-surface-variant="primary"] {
--interactive-surface-bg: var(
--interactive-surface-variant-primary-bg,
var(--variant-primary-bg, rgb(15 79 127))
Expand All @@ -341,7 +376,8 @@
--interactive-surface-darken-active: 0.97;
}

.interactive-surface.variant-secondary {
.interactive-surface.variant-secondary,
.interactive-surface[data-surface-variant="secondary"] {
--interactive-surface-bg: var(
--interactive-surface-variant-secondary-bg,
var(--variant-secondary-bg, rgb(198 220 237))
Expand All @@ -358,7 +394,8 @@
--interactive-surface-darken-active: 0.99;
}

.interactive-surface.variant-accent {
.interactive-surface.variant-accent,
.interactive-surface[data-surface-variant="accent"] {
--interactive-surface-bg: var(
--interactive-surface-variant-accent-bg,
var(--variant-accent-bg, rgb(201 240 237))
Expand All @@ -375,7 +412,8 @@
--interactive-surface-darken-active: 0.96;
}

.interactive-surface.variant-subtle {
.interactive-surface.variant-subtle,
.interactive-surface[data-surface-variant="subtle"] {
--interactive-surface-bg: var(
--interactive-surface-variant-subtle-bg,
var(--variant-subtle-bg, rgb(219 231 243))
Expand All @@ -392,7 +430,8 @@
--interactive-surface-darken-active: 1;
}

.interactive-surface.variant-warning {
.interactive-surface.variant-warning,
.interactive-surface[data-surface-variant="warning"] {
--interactive-surface-bg: var(
--interactive-surface-variant-warning-bg,
var(--variant-warning-bg, rgb(255 227 186))
Expand All @@ -409,7 +448,8 @@
--interactive-surface-darken-active: 0.95;
}

.interactive-surface.variant-danger {
.interactive-surface.variant-danger,
.interactive-surface[data-surface-variant="danger"] {
--interactive-surface-bg: var(
--interactive-surface-variant-danger-bg,
var(--variant-danger-bg, rgb(255 211 213))
Expand Down
Loading