-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
788 lines (706 loc) · 31.9 KB
/
Copy pathindex.html
File metadata and controls
788 lines (706 loc) · 31.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Interactive Surface CSS State Lab</title>
<meta
name="description"
content="A state-first lab for testing accessible interaction behavior with Interactive Surface CSS."
/>
<meta name="author" content="Kyle Foster" />
<meta name="robots" content="index, follow" />
<meta
name="theme-color"
media="(prefers-color-scheme: light)"
content="rgb(244 248 251)"
/>
<meta
name="theme-color"
media="(prefers-color-scheme: dark)"
content="rgb(9 19 28)"
/>
<meta name="color-scheme" content="light dark" />
<link rel="icon" href="./assets/favicon.ico" sizes="any" />
<link
rel="icon"
type="image/png"
sizes="16x16"
href="./assets/favicon-16x16.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="./assets/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="48x48"
href="./assets/favicon-48x48.png"
/>
<link
rel="icon"
type="image/png"
sizes="64x64"
href="./assets/favicon-64x64.png"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="./assets/apple-touch-icon.png"
/>
<link rel="manifest" href="./assets/site.webmanifest" />
<meta name="msapplication-TileColor" content="#09131c" />
<meta name="msapplication-config" content="./assets/browserconfig.xml" />
<link
rel="canonical"
href="https://foscat.github.io/Interactive-Surface-CSS/"
/>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareSourceCode",
"name": "Interactive Surface CSS",
"description": "A framework-agnostic CSS interaction primitive with accessible state behavior.",
"codeRepository": "https://github.com/Foscat/Interactive-Surface-CSS",
"programmingLanguage": "CSS",
"license": "https://opensource.org/licenses/MIT",
"url": "https://foscat.github.io/Interactive-Surface-CSS/"
}
</script>
<link rel="stylesheet" href="./standalone-preset.css" />
<link rel="stylesheet" href="./demo/demo.css" />
</head>
<body>
<a class="skip-link" href="#state-lab">Skip to the state lab</a>
<main id="main-content">
<!-- Hero: concise positioning plus the primary proof and documentation paths. -->
<header class="hero">
<p class="eyebrow">State-first interaction primitive</p>
<h1>Interactive Surface CSS</h1>
<p class="hero-lead">
Inspect native controls as their pressed, selected, busy, current, and
disabled states change in real time.
</p>
<nav class="hero-links" aria-label="Primary documentation">
<a
class="interactive-surface surface-control variant-primary hero-primary-link"
href="https://foscat.github.io/interface-systems-lab/"
data-primary-control
>
Open the Interface Systems Lab
</a>
<a
class="interactive-surface surface-control variant-secondary"
href="https://github.com/Foscat/Interactive-Surface-CSS"
data-primary-control
>
View source
</a>
<a
class="interactive-surface surface-control variant-subtle"
href="https://www.npmjs.com/package/interactive-surface-css"
data-primary-control
>
Open npm
</a>
</nav>
</header>
<!-- Ecosystem ownership stays explicit so consumers choose the correct layer. -->
<section
id="ecosystem-ownership"
class="panel"
aria-labelledby="ecosystem-heading"
>
<div class="section-heading">
<p class="eyebrow">One system, clear boundaries</p>
<h2 id="ecosystem-heading">Ecosystem ownership</h2>
<p>
Each package remains useful on its own while owning one distinct
part of the interface contract.
</p>
</div>
<div class="ownership-grid">
<article>
<h3>Layout Style CSS</h3>
<p class="ownership-role">structure and geometry</p>
<p>
Responsive composition, spacing relationships, and page-level
layout.
</p>
</article>
<article>
<h3>UI Style Kit CSS</h3>
<p class="ownership-role">visual paint and themes</p>
<p>Color, typography, borders, elevation, and theme expression.</p>
</article>
<article>
<h3>Interactive Surface CSS</h3>
<p class="ownership-role">interaction states</p>
<p>
Focus, hover, active, pressed, selected, busy, and disabled
behavior.
</p>
</article>
</div>
</section>
<!-- Live proof precedes configuration so the library's purpose is immediately testable. -->
<section
id="state-lab"
class="panel state-lab"
aria-labelledby="state-lab-heading"
>
<div class="section-heading">
<p class="eyebrow">Live semantic proof</p>
<h2 id="state-lab-heading">Interaction state lab</h2>
<p>
Use the controls, then inspect their native attributes. No
pseudo-class is faked.
</p>
</div>
<div class="state-grid">
<article class="example-card">
<h3>Action</h3>
<button
type="button"
class="interactive-surface surface-control variant-primary"
data-example="action"
>
Run action
</button>
<code><button></code>
</article>
<article class="example-card">
<h3>Toggle</h3>
<button
type="button"
class="interactive-surface surface-control variant-secondary"
aria-pressed="false"
data-example="toggle"
>
Notifications <span id="toggle-state">Off</span>
</button>
<code>aria-pressed</code>
</article>
<article class="example-card">
<h3>Current</h3>
<a
class="interactive-surface surface-control variant-subtle"
href="#state-lab"
aria-current="page"
data-example="current"
>
State lab
</a>
<code>aria-current="page"</code>
</article>
<article class="example-card tab-example">
<h3>Selected</h3>
<div
id="state-tabs"
class="tab-list"
role="tablist"
aria-label="State views"
>
<button
id="resting-tab"
type="button"
class="interactive-surface surface-control size-sm"
role="tab"
aria-selected="true"
aria-controls="resting-panel"
>
Resting
</button>
<button
id="selected-tab"
type="button"
class="interactive-surface surface-control size-sm variant-accent"
role="tab"
aria-selected="false"
aria-controls="selected-panel"
tabindex="-1"
data-example="selected"
>
Selected
</button>
</div>
<p
id="resting-panel"
class="tab-panel"
role="tabpanel"
aria-labelledby="resting-tab"
>
Resting view
</p>
<p
id="selected-panel"
class="tab-panel"
role="tabpanel"
aria-labelledby="selected-tab"
hidden
>
Selected view
</p>
</article>
<article class="example-card">
<h3>Loading</h3>
<button
type="button"
class="interactive-surface surface-control variant-accent"
aria-busy="false"
data-example="loading"
>
Run loading proof
</button>
<code>aria-busy</code>
</article>
<article class="example-card">
<h3>Disabled</h3>
<button
type="button"
class="interactive-surface surface-control"
disabled
data-example="disabled"
>
Unavailable action
</button>
<code>disabled</code>
</article>
<article class="example-card">
<h3>Variant</h3>
<button
type="button"
class="interactive-surface surface-control variant-primary"
data-example="variant"
>
Primary variant
</button>
<code>.variant-primary</code>
</article>
<article class="example-card">
<h3>Level</h3>
<button
type="button"
class="interactive-surface surface-control"
data-surface-level="2"
data-example="level"
>
Elevated level 2
</button>
<code>data-surface-level="2"</code>
</article>
<article class="example-card">
<h3>Icon</h3>
<button
type="button"
class="interactive-surface icon-only variant-secondary"
aria-label="Show interaction details"
data-example="icon"
>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path
d="M11 7h2v2h-2zm0 4h2v6h-2zm1-9a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm0 18a8 8 0 1 1 0-16 8 8 0 0 1 0 16z"
></path>
</svg>
</button>
<code>aria-label + icon-only</code>
</article>
</div>
</section>
<!-- Accessibility guidance describes the behavior demonstrated above. -->
<section
id="accessibility-guidance"
class="panel"
aria-labelledby="accessibility-heading"
>
<div class="section-heading">
<p class="eyebrow">Built on browser semantics</p>
<h2 id="accessibility-heading">Accessibility guidance</h2>
</div>
<div class="guidance-grid">
<article>
<h3>Start with native controls</h3>
<p>
Use buttons for actions and anchors for navigation so keyboard and
assistive technology behavior is preserved.
</p>
</article>
<article>
<h3>Expose persistent state</h3>
<p>
Pair visual treatment with <code>aria-pressed</code>,
<code>aria-selected</code>, <code>aria-current</code>, or
<code>aria-busy</code>.
</p>
</article>
<article>
<h3>Keep focus reliable</h3>
<p>
Disabled controls stay non-interactive, modal focus stays
contained, and closing restores the initiating control.
</p>
</article>
</div>
</section>
<!-- Entry points make the layered package contract easy to choose. -->
<section
id="entry-points"
class="panel"
aria-labelledby="entry-points-heading"
>
<div class="section-heading">
<p class="eyebrow">Choose the right ownership boundary</p>
<h2 id="entry-points-heading">Entry points</h2>
</div>
<div class="entry-grid">
<article>
<h3>Standalone preset</h3>
<p>
Complete paint, geometry, and interaction behavior for a
framework-agnostic page.
</p>
<pre><code>@import "interactive-surface-css/standalone-preset.css";</code></pre>
</article>
<article>
<h3>State core</h3>
<p>
Interaction mechanics only when a host design system already owns
presentation.
</p>
<pre><code>@import "interactive-surface-css/state-core.css";</code></pre>
</article>
<article>
<h3>Compatibility bundle</h3>
<p>The established package entry point for current consumers.</p>
<pre><code>@import "interactive-surface-css/interactive-surface.css";</code></pre>
</article>
</div>
</section>
<!-- Token tooling is intentionally advanced and follows the live state proof. -->
<section
id="advanced-tools"
class="panel"
aria-labelledby="advanced-tools-heading"
>
<div class="section-heading">
<p class="eyebrow">Advanced configuration</p>
<h2 id="advanced-tools-heading">Token tools</h2>
<p>
Edit a value, import a small CSS override, or export the current
overrides for further testing.
</p>
</div>
<div class="token-grid">
<article class="token-card" data-token="--interactive-surface-bg">
<div>
<h3>Surface background</h3>
<code class="token-name">--interactive-surface-bg</code>
</div>
<p class="token-value" data-token-value>rgb(255 255 255)</p>
<button
type="button"
class="interactive-surface surface-control size-sm variant-subtle"
aria-label="Edit --interactive-surface-bg"
data-token-edit
data-primary-control
>
Edit value
</button>
</article>
<article
class="token-card"
data-token="--interactive-surface-focus-ring-color"
>
<div>
<h3>Focus ring</h3>
<code class="token-name"
>--interactive-surface-focus-ring-color</code
>
</div>
<p class="token-value" data-token-value>rgb(30 91 240)</p>
<button
type="button"
class="interactive-surface surface-control size-sm variant-subtle"
aria-label="Edit --interactive-surface-focus-ring-color"
data-token-edit
>
Edit value
</button>
</article>
</div>
<div class="token-actions">
<label class="file-field" for="tokenCssImport">
<span>Import token CSS</span>
<input
id="tokenCssImport"
class="interactive-surface variant-subtle"
type="file"
accept=".css,text/css"
/>
</label>
<button
id="tokenCssCopy"
type="button"
class="interactive-surface surface-control variant-secondary"
>
Copy token CSS
</button>
<button
id="tokenCssDownload"
type="button"
class="interactive-surface surface-control variant-primary"
>
Download token CSS
</button>
</div>
</section>
<!-- The rendered README remains reference material rather than the primary demo experience. -->
<section
id="readme-reference"
class="panel"
aria-labelledby="readme-reference-heading"
>
<div class="section-heading">
<p class="eyebrow">Reference</p>
<h2 id="readme-reference-heading">README reference</h2>
<p>
The repository README renders here with an embedded fallback for
local and offline use.
</p>
</div>
<nav class="reference-links" aria-label="Reference links">
<a href="https://github.com/Foscat/Interactive-Surface-CSS#readme"
>README on GitHub</a
>
<a
href="https://github.com/Foscat/Interactive-Surface-CSS/wiki/Accessibility"
>Accessibility wiki</a
>
<a
href="https://github.com/Foscat/Interactive-Surface-CSS/wiki/Token-Reference"
>Token reference</a
>
</nav>
<article
id="readmeContent"
class="markdown-body"
aria-label="Rendered README"
>
<p>Preparing the embedded README reference…</p>
</article>
</section>
</main>
<div
id="demoStatus"
class="live-status"
role="status"
aria-live="polite"
aria-atomic="true"
tabindex="-1"
></div>
<!-- The native dialog is outside main so inert background content never contains the active modal. -->
<dialog id="token-editor-dialog" aria-labelledby="tokenEditorTitle">
<form id="tokenEditor" class="token-editor" novalidate>
<div class="dialog-heading">
<p class="eyebrow">Advanced token override</p>
<h2 id="tokenEditorTitle">Edit token</h2>
</div>
<label class="token-field" for="tokenEditorName">
<span>Token name</span>
<input id="tokenEditorName" type="text" readonly tabindex="-1" />
</label>
<label class="token-field" for="tokenEditorValue">
<span>Token value</span>
<input
id="tokenEditorValue"
type="text"
autocomplete="off"
required
/>
</label>
<div class="dialog-actions">
<button
type="submit"
class="interactive-surface surface-control variant-primary"
>
Apply token
</button>
<button
id="tokenEditorCancel"
type="button"
class="interactive-surface surface-control variant-subtle"
>
Cancel token edit
</button>
</div>
</form>
</dialog>
<!-- prettier-ignore -->
<script id="embeddedReadme" type="text/markdown">
# Interactive Surface CSS
[](https://www.npmjs.com/package/interactive-surface-css)
[](https://github.com/Foscat/Interactive-Surface-CSS/blob/main/LICENSE)
Framework-agnostic CSS for reliable hover, focus, press, selected, current, loading, disabled, and motion affordances on interactive controls. Use it alone or as the interaction layer beside your existing layout and theme system.
Version 1.5.0 is a release candidate in this repository until its npm release is published. Existing 1.x imports, selectors, data hooks, ARIA hooks, and tokens remain supported.
The package targets Node.js 20+ for npm installs and local validation. CI proves the minimum Node 20 lane and the preferred Node 22 lane before release.
## Start here
- [Live standalone demo](https://foscat.github.io/Interactive-Surface-CSS/)
- [Interface Systems Lab](https://foscat.github.io/interface-systems-lab/) — integrated proof with all three CSS libraries
- [GitHub Wiki](https://github.com/Foscat/Interactive-Surface-CSS/wiki/Home)
- [Installation guide](https://github.com/Foscat/Interactive-Surface-CSS/wiki/Installation-and-Usage)
- [API reference](https://github.com/Foscat/Interactive-Surface-CSS/wiki/API-Reference)
- [Token reference](https://github.com/Foscat/Interactive-Surface-CSS/wiki/Token-Reference)
- [Accessibility guide](https://github.com/Foscat/Interactive-Surface-CSS/wiki/Accessibility)
## Ownership
Each package remains independently useful. Use one library, use two compatible libraries, or use all three according to the layers your application needs.
| Library | Owns | Does not own |
| ------------------------- | ------------------------------------------------------------------------------ | ----------------------------------- |
| `interactive-surface-css` | Interaction states, focus visibility, state precedence, and interaction motion | Page layout or an application theme |
| `ui-style-kit-css` | Theme paint, modes, component appearance, and visual tokens | Page layout or interaction behavior |
| `layout-style-css` | Page structure, layout recipes, and geometry | Theme paint or interaction behavior |
## 60-second standalone setup
Install the package:
```bash
npm install interactive-surface-css
```
Import the portable, direct CSS preset. It includes the neutral standalone presentation and the complete state layer:
```js
import "interactive-surface-css/standalone-preset.css";
```
Add the base class to a native control:
```html
<button class="interactive-surface variant-primary" type="button">
Save changes
</button>
```
For a no-build page, pin the release:
```html
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/interactive-surface-css@1.5.0/standalone-preset.css"
/>
```
The equivalent unpkg URL is `https://unpkg.com/interactive-surface-css@1.5.0/standalone-preset.css`. To follow future releases deliberately, use `https://cdn.jsdelivr.net/npm/interactive-surface-css@latest/standalone-preset.css` — unpinned opt-in.
## Semantic recipes
Prefer native elements, then reflect persistent application state through ARIA:
```html
<!-- Action -->
<button class="interactive-surface variant-primary" type="button">
Publish
</button>
<!-- Toggle: update aria-pressed when the value changes -->
<button class="interactive-surface" type="button" aria-pressed="true">
Pinned
</button>
<!-- Current navigation item: any non-false aria-current value is supported -->
<a class="interactive-surface" href="/account" aria-current="page">Account</a>
<!-- Selected item in a composite widget -->
<button class="interactive-surface" role="tab" aria-selected="true">
Details
</button>
<!-- Loading: update the accessible label or nearby status text as needed -->
<button class="interactive-surface" type="button" aria-busy="true">
Saving…
</button>
<!-- Prefer native disabled when the control cannot activate -->
<button class="interactive-surface" type="button" disabled>Unavailable</button>
<!-- Native file inputs opt in on the host; the selector button follows the same tokens -->
<input class="interactive-surface variant-subtle" type="file" />
<!-- Variant and level hooks are safe for renderers and companion bridges -->
<button
class="interactive-surface"
type="button"
data-surface-variant="accent"
data-surface-level="2"
>
Continue
</button>
<!-- Icon-only controls need an accessible name; the icon itself is decorative -->
<button
class="interactive-surface icon-only"
type="button"
aria-label="Open settings"
>
<svg aria-hidden="true" data-icon-role="dark" viewBox="0 0 24 24">…</svg>
</button>
```
Native `disabled` is preferred because the browser suppresses focus and activation. CSS can only communicate disabled-looking state: consumers must suppress activation for `aria-disabled="true"` and `.is-disabled` controls in their event handling.
## Entry points
| Import | Presentation | Best for |
| ----------------------------------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `import "interactive-surface-css/standalone-preset.css";` | State core plus neutral paint, variants, levels, icon sizing, and preset token defaults | New standalone integrations |
| `import "interactive-surface-css/state-core.css";` | Interaction mechanics and neutral core token fallbacks only | Existing design systems that already own paint and geometry |
| `import "interactive-surface-css/interactive-surface.css";` | Complete standalone compatibility bundle | Existing direct-CSS 1.x consumers |
| `import "interactive-surface-css";` | JavaScript entry that imports the complete compatibility bundle | Existing bundlers configured for CSS imports |
`standalone-preset.css` and `interactive-surface.css` are generated from the same authored modules and are behaviorally equivalent in 1.5.0. The compatibility paths remain stable; no 1.x migration is required.
The package `main` and `module` fields preserve the CommonJS and ESM entries; both load `interactive-surface.css`. The `style`, `unpkg`, and `jsdelivr` fields also resolve to that complete compatibility bundle.
## Compact API
| Kind | Public hooks |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Base | `.interactive-surface` |
| Size | `.size-sm`, default medium, `.size-lg` |
| Transient | pointer `:hover`, keyboard `:focus-visible`, `:active` |
| Persistent | `.is-active`, `aria-pressed="true"`, `aria-pressed="mixed"`, any non-false `aria-current`, `aria-selected="true"` |
| Loading | `aria-busy="true"`, `.is-loading` |
| Disabled | native `:disabled`, `aria-disabled="true"`, `.is-disabled` |
| Variant | `.variant-primary`, `.variant-secondary`, `.variant-accent`, `.variant-subtle`, `.variant-warning`, `.variant-danger`, or matching `data-surface-variant` values |
| Level | `data-surface-level="1"`, `"2"`, or `"3"` |
| Icon | `.icon-only`; child `data-icon-role="light"`, `"dark"`, `"accessibility"`, or the legacy role classes |
| Native subcontrol | `input[type="file"].interactive-surface::file-selector-button` inherits surface paint and hover/active feedback |
Disabled > busy/loading > transient `:active` > pressed/selected/current > `:hover` > base. The `:focus-visible` ring is orthogonal to that precedence, so keyboard focus remains visible without replacing the active interaction state. The state layer preserves static meaning under reduced motion and uses system-color affordances in forced-colors mode. Interaction lift uses the individual `translate` property, so consumer-owned `transform`, `scale`, and `rotate` declarations can coexist.
For selector details and responsibilities, see the [complete API reference](https://github.com/Foscat/Interactive-Surface-CSS/wiki/API-Reference).
## Customize tokens
`state-core.css` provides neutral defaults for state-layer color and opacity, focus rings, motion, persistent states, disabled state, and loading state. `standalone-preset.css` additionally supplies base paint, lift and shadows, variants, surface levels, icon roles, and the 44 × 44px icon-control geometry.
```css
.save-action {
--interactive-surface-focus-ring-color: rgb(0 95 115);
--interactive-surface-state-layer-color: rgb(0 45 55);
--interactive-surface-state-layer-hover-opacity: 0.1;
--interactive-surface-transition-property:
translate, box-shadow, outline-color;
--interactive-surface-transition-duration: 120ms;
--interactive-surface-transition-easing: cubic-bezier(0.2, 0, 0.2, 1);
--interactive-surface-transition-delay: 0ms;
}
```
The transition defaults are `translate, box-shadow, outline-color`, the established default motion duration, the standard easing curve, and zero delay. Existing `--interactive-surface-motion-*`, `--interactive-surface-ease-*`, `--motion-*`, and `--ease-*` fallbacks remain supported. All public custom properties use the `--interactive-surface-*` namespace. See the [token ownership tables and full reference](https://github.com/Foscat/Interactive-Surface-CSS/wiki/Token-Reference).
## Accessibility responsibilities
The CSS package provides visible keyboard focus, persistent-state treatment, disabled precedence, reduced-motion behavior, higher-contrast behavior, forced-colors affordances, hover gating for capable pointers, and standalone icon target sizing. The `:focus-visible` ring remains orthogonal across every focusable non-disabled interaction state.
Applications still own semantics and behavior:
- Use `<button>` for actions and `<a href>` for navigation.
- Update `aria-pressed`, `aria-current`, `aria-selected`, and `aria-busy` when application state changes.
- Prefer native `disabled`. If a custom control uses `aria-disabled="true"` or `.is-disabled`, suppress pointer, keyboard, and programmatic activation.
- Give icon-only controls an accessible name, normally with `aria-label`, and hide decorative SVG content from assistive technology.
- Implement the keyboard model for composite widgets such as tabs; CSS does not add runtime behavior.
Read the [accessibility guide](https://github.com/Foscat/Interactive-Surface-CSS/wiki/Accessibility) for complete examples.
## Pair with UI Style Kit CSS
When UI Style Kit owns visual paint, import its opt-in bridge and the state-only core:
```js
import "ui-style-kit-css/with-bridge.css";
import "interactive-surface-css/state-core.css";
```
The bridge maps active UI Style Kit theme and mode values into the `--interactive-surface-*` contract. Interactive Surface keeps ownership of focus, hover, pressed, selected, current, loading, disabled, and motion behavior. UI Style Kit remains optional: this package declares no runtime or peer dependency on it, and the documented import order keeps its paint layer before `state-core.css`.
## Use all three libraries
Use the established order so paint, interaction, and structure retain clear ownership:
```js
import "ui-style-kit-css/with-bridge.css";
import "interactive-surface-css/state-core.css";
import "layout-style-css/bridge.css";
import "layout-style-css";
```
The [Interface Systems Lab](https://foscat.github.io/interface-systems-lab/) is the canonical integrated example. Each package remains optional: use one library, use two compatible libraries, or use all three.
## Support and project links
- Browser behavior: current Chromium, Firefox, and WebKit are covered by the full Playwright gate; forced-colors and platform-specific behavior are tested where supported.
- [Testing and quality guide](https://github.com/Foscat/Interactive-Surface-CSS/wiki/Testing-and-Quality)
- [Changelog](https://github.com/Foscat/Interactive-Surface-CSS/blob/main/CHANGELOG.md)
- [Contributing](https://github.com/Foscat/Interactive-Surface-CSS/blob/main/CONTRIBUTING.md)
- [Code of Conduct](https://github.com/Foscat/Interactive-Surface-CSS/blob/main/CODE_OF_CONDUCT.md)
- [Security policy](https://github.com/Foscat/Interactive-Surface-CSS/blob/main/SECURITY.md)
- [MIT License](https://github.com/Foscat/Interactive-Surface-CSS/blob/main/LICENSE)
</script>
<script src="./demo/demo.js" defer></script>
</body>
</html>