Skip to content

Foscat/Interactive-Surface-CSS

Interactive Surface

Interactive Surface is a framework-agnostic CSS interaction primitive for buttons, cards, icon controls, and similar click targets.

It provides consistent hover, focus-visible, active, press, and disabled behavior with token-driven theming, accessibility guardrails, and minimal integration friction.

Documentation

Project docs live in this repository:

Community and governance docs:

Package

  • Package name: interactive-surface-css
  • Primary stylesheet: interactive-surface.css
  • JavaScript entry: index.js (imports CSS for bundler-friendly usage)
  • Live demo: https://foscat.github.io/Interactive-Surface-CSS/

Install:

npm install interactive-surface-css

Import:

import "interactive-surface-css";

Or import CSS directly:

import "interactive-surface-css/interactive-surface.css";

Note: The JavaScript entry imports CSS, so it should be used in bundlers or toolchains that support CSS imports. If you want the most portable, framework-agnostic path, import interactive-surface-css/interactive-surface.css directly. The package supports both approaches to accommodate different project setups and preferences.

Webpack:

  1. Install loaders:

    npm install -D css-loader style-loader
  2. Configure webpack.config.js:

    module.exports = {
      module: {
        rules: [
          {
            test: /\.css$/i,
            use: ["style-loader", "css-loader"]
          }
        ]
      }
    };
  3. Import in your app entry:

    import "interactive-surface-css";

CDN:

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

Quick Start

<button class="interactive-surface">Save</button>
<button class="interactive-surface size-lg variant-primary">Continue</button>
<button class="interactive-surface icon-only" aria-label="Settings">
  <svg aria-hidden="true" viewBox="0 0 24 24">...</svg>
</button>

Live demo: Interactive Surface Demo

The demo page is a practical customization playground for this library:

  • It provides guided token editing controls instead of freehand CSS typing.
  • It supports importing and exporting token CSS so teams can reuse exact values.
  • It helps reduce manual entry mistakes when creating app-level theme overrides.

Class API

Base:

  • .interactive-surface

Size modifiers:

  • .size-sm
  • .size-lg
  • medium is default when no size class is set

State helpers:

  • .is-active
  • .is-disabled

Semantic states:

  • [aria-pressed="true"]
  • [aria-disabled="true"]
  • :disabled

Visual variants:

  • .variant-primary
  • .variant-secondary
  • .variant-accent
  • .variant-subtle
  • .variant-warning
  • .variant-danger

Icon pattern:

  • .icon-only

Token Contract

Preferred token namespace:

  • --interactive-surface-*

The package also supports legacy fallback tokens and semantic fallback tokens. Full details and examples are in Token Reference.

Accessibility

Built-in support includes:

  • :focus-visible behavior with fallback handling
  • reduced-motion preference handling
  • high-contrast and forced-colors handling
  • ARIA pressed/disabled styling
  • 44x44 minimum target size for .icon-only

See Accessibility for implementation guidance.

Testing

npm run check:no-hex-colors
npm run lint:css
npm test
npm run test:chromium
npm run pack:dry

Publishing

This repo is configured for release-driven npm publish through GitHub Actions at .github/workflows/npm-publish.yml.

Release checklist:

  1. Add repository secret NPM_TOKEN (npm token with publish rights).
  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.1.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
    • https://unpkg.com/interactive-surface-css@<version>/interactive-surface.css

Manual fallback:

npm adduser
npm publish --access public

Guardrail

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.