actual-css 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -1
- package/README.md +25 -8
- package/dist/actual-themes.min.css +1 -0
- package/dist/actual.css +1492 -931
- package/dist/actual.js +1 -3
- package/dist/actual.min.css +1 -1
- package/dist/optional.css +1048 -0
- package/dist/optional.min.css +1 -0
- package/docs/design-notes/browser-support.md +95 -6
- package/docs/design-notes/cascade-layer.md +1 -1
- package/docs/design-notes/enhancement-contract.md +101 -0
- package/docs/design-notes/forced-colors.md +105 -0
- package/docs/design-notes/naming.md +3 -1
- package/docs/design-notes/platform-alignment.md +492 -0
- package/docs/design-notes/references.md +8 -0
- package/docs/design-notes/widget-primitives.md +119 -0
- package/docs/navigation.json +135 -0
- package/docs/pages/components/accordion.md +80 -0
- package/docs/pages/components/alert.md +203 -0
- package/docs/pages/components/aura.md +35 -0
- package/docs/pages/components/avatar.md +95 -0
- package/docs/pages/components/badge.md +102 -0
- package/docs/pages/components/breadcrumb.md +19 -0
- package/docs/pages/components/button.md +205 -0
- package/docs/pages/components/card.md +183 -0
- package/docs/pages/components/chat.md +50 -0
- package/docs/pages/components/dialog.md +367 -0
- package/docs/pages/components/drawer.md +113 -0
- package/docs/pages/components/fab.md +118 -0
- package/docs/pages/components/key.md +33 -0
- package/docs/pages/components/meter.md +23 -0
- package/docs/pages/components/navbar.md +92 -0
- package/docs/pages/components/pagination.md +55 -0
- package/docs/pages/components/progress.md +29 -0
- package/docs/pages/components/skeleton.md +38 -0
- package/docs/pages/components/spinner.md +93 -0
- package/docs/pages/components/table.md +102 -0
- package/docs/pages/enhancements/flyout.md +391 -0
- package/docs/pages/enhancements/overview.md +36 -0
- package/docs/pages/enhancements/scrollspy.md +71 -0
- package/docs/pages/enhancements/status-bar.md +72 -0
- package/docs/pages/enhancements/tabs.md +80 -0
- package/docs/pages/enhancements/tooltip.md +130 -0
- package/docs/pages/examples/overview.md +41 -0
- package/docs/pages/forms/choice-cards.md +102 -0
- package/docs/pages/forms/controls.md +32 -0
- package/docs/pages/forms/detached-actions.md +59 -0
- package/docs/pages/forms/floating-field.md +88 -0
- package/docs/pages/forms/form-actions.md +56 -0
- package/docs/pages/forms/input-policy.md +91 -0
- package/docs/pages/forms/joined-controls.md +37 -0
- package/docs/pages/forms/labels.md +42 -0
- package/docs/pages/forms/otp.md +50 -0
- package/docs/pages/forms/overview.md +188 -0
- package/docs/pages/forms/password.md +40 -0
- package/docs/pages/forms/range.md +17 -0
- package/docs/pages/forms/states-and-sizes.md +321 -0
- package/docs/pages/forms/sticky-actions.md +187 -0
- package/docs/pages/forms/switch.md +31 -0
- package/docs/pages/forms/validation.md +241 -0
- package/docs/{mental-model.md → pages/foundations/philosophy.md} +33 -18
- package/docs/pages/foundations/theming.md +92 -0
- package/docs/pages/foundations/tokens.md +421 -0
- package/docs/{typography.md → pages/foundations/typography.md} +27 -9
- package/docs/pages/guides/bootstrap.md +438 -0
- package/docs/pages/guides/introduction.md +157 -0
- package/docs/pages/guides/optional-components.md +33 -0
- package/docs/pages/guides/progressive-enhancement.md +686 -0
- package/docs/pages/guides/tailwind.md +455 -0
- package/docs/pages/layout/app-shell.md +25 -0
- package/docs/pages/layout/center.md +29 -0
- package/docs/pages/layout/cluster.md +57 -0
- package/docs/pages/layout/frame.md +30 -0
- package/docs/pages/layout/grid.md +142 -0
- package/docs/pages/layout/list.md +41 -0
- package/docs/pages/layout/media.md +21 -0
- package/docs/pages/layout/overview.md +37 -0
- package/docs/pages/layout/scroll-snap.md +54 -0
- package/docs/pages/layout/sidebar-layout.md +27 -0
- package/docs/pages/layout/stack.md +18 -0
- package/docs/pages/layout/switcher.md +36 -0
- package/docs/pages/layout/topbar.md +23 -0
- package/docs/pages/patterns/actions.md +52 -0
- package/docs/pages/patterns/code-block.md +36 -0
- package/docs/pages/patterns/exclusive-accordion.md +16 -0
- package/docs/pages/patterns/hierarchical-nav.md +26 -0
- package/docs/pages/patterns/icon-input.md +25 -0
- package/docs/pages/patterns/intent-nesting.md +3 -0
- package/docs/pages/patterns/navigation-list.md +235 -0
- package/docs/pages/patterns/overview.md +58 -0
- package/docs/pages/patterns/structured-lists.md +47 -0
- package/docs/pages/utilities/accessibility.md +34 -0
- package/docs/pages/utilities/flex.md +105 -0
- package/docs/pages/utilities/links.md +18 -0
- package/docs/pages/utilities/list.md +31 -0
- package/docs/pages/utilities/overflow.md +43 -0
- package/docs/pages/utilities/overview.md +18 -0
- package/docs/pages/utilities/shape.md +25 -0
- package/docs/pages/utilities/sizing-wrapping.md +115 -0
- package/docs/pages/utilities/spacing.md +79 -0
- package/docs/pages/utilities/text.md +43 -0
- package/llms.txt +10 -4
- package/package.json +30 -16
- package/src/css/actual.css +12 -1
- package/src/css/base.css +3 -9
- package/src/css/components/accordion.css +18 -30
- package/src/css/components/actions.css +4 -1
- package/src/css/components/alert.css +162 -49
- package/src/css/components/avatar.css +16 -10
- package/src/css/components/badge.css +82 -39
- package/src/css/components/breadcrumb.css +7 -15
- package/src/css/components/busy.css +7 -0
- package/src/css/components/button.css +82 -38
- package/src/css/components/card.css +17 -37
- package/src/css/components/drawer.css +30 -13
- package/src/css/components/flyout.css +39 -110
- package/src/css/components/index.css +1 -0
- package/src/css/components/join.css +14 -3
- package/src/css/components/key.css +2 -0
- package/src/css/components/menu.css +125 -0
- package/src/css/components/meter.css +0 -23
- package/src/css/components/modal.css +162 -7
- package/src/css/components/navbar.css +17 -18
- package/src/css/components/overline.css +0 -7
- package/src/css/components/pagination.css +1 -1
- package/src/css/components/progress.css +8 -26
- package/src/css/components/scrollspy.css +2 -2
- package/src/css/components/skeleton.css +7 -0
- package/src/css/components/spinner.css +10 -11
- package/src/css/components/status-bar.css +18 -28
- package/src/css/components/surface.css +2 -8
- package/src/css/components/tab.css +20 -48
- package/src/css/components/table.css +7 -5
- package/src/css/components/tooltip.css +14 -7
- package/src/css/focus.css +16 -3
- package/src/css/forms/choice-card.css +13 -15
- package/src/css/forms/choice.css +21 -28
- package/src/css/forms/control.css +16 -26
- package/src/css/forms/custom-select.css +9 -11
- package/src/css/forms/form-actions.css +14 -3
- package/src/css/forms/form.css +7 -4
- package/src/css/forms/index.css +5 -4
- package/src/css/forms/input-icon.css +30 -12
- package/src/css/forms/native.css +48 -33
- package/src/css/forms/range.css +103 -0
- package/src/css/forms/select.css +8 -8
- package/src/css/forms/switch.css +14 -13
- package/src/css/forms/validation.css +32 -5
- package/src/css/grid.css +19 -18
- package/src/css/intents.css +2 -2
- package/src/css/layout.css +29 -1
- package/src/css/optional/aura.css +93 -0
- package/src/css/optional/chat.css +105 -0
- package/src/css/optional/fab.css +104 -0
- package/src/css/optional/floating-field.css +87 -0
- package/src/css/optional/index.css +5 -0
- package/src/css/optional/layout-extra.css +7 -4
- package/src/css/optional/otp.css +115 -0
- package/src/css/optional/scroll-snap.css +6 -0
- package/src/css/optional/scroller.css +19 -7
- package/src/css/optional/typography-fluid.css +5 -0
- package/src/css/optional/utilities-extra.css +17 -2
- package/src/css/prose.css +18 -8
- package/src/css/reset.css +0 -10
- package/src/css/theme.css +29 -13
- package/src/css/themes/cyberpunk.css +1 -1
- package/src/css/themes/dim.css +3 -3
- package/src/css/themes/edge.css +11 -37
- package/src/css/themes/index.css +3 -3
- package/src/css/themes/{dark.css → indigo.css} +5 -4
- package/src/css/themes/neon.css +5 -5
- package/src/css/themes/square.css +0 -3
- package/src/css/tokens.css +81 -21
- package/src/css/utilities.css +8 -10
- package/src/css/variants.css +37 -65
- package/src/js/command.js +1 -3
- package/src/js/context-menu.js +72 -53
- package/src/js/dialog.js +68 -79
- package/src/js/dismiss.js +15 -0
- package/src/js/enhance.js +76 -19
- package/src/js/enhancement-loader.js +301 -0
- package/src/js/events.js +3 -0
- package/src/js/filter.js +3 -3
- package/src/js/floating.js +101 -131
- package/src/js/flyout.js +106 -96
- package/src/js/focus-group.js +116 -0
- package/src/js/focus.js +6 -1
- package/src/js/index.js +4 -2
- package/src/js/input.js +1 -1
- package/src/js/keys.js +27 -0
- package/src/js/mask.js +7 -6
- package/src/js/menu.js +89 -35
- package/src/js/parse-config.js +47 -0
- package/src/js/password.js +9 -1
- package/src/js/scrollspy.js +133 -62
- package/src/js/selectors.js +14 -31
- package/src/js/status.js +16 -14
- package/src/js/surface.js +219 -125
- package/src/js/tab.js +46 -66
- package/src/js/tooltip.js +53 -31
- package/src/js/validation.js +45 -51
- package/dist/actual.js.map +0 -31
- package/docs/README.md +0 -26
- package/docs/components.md +0 -860
- package/docs/design-notes/previous-work.md +0 -5
- package/docs/forms.md +0 -1265
- package/docs/javascript.md +0 -426
- package/docs/layout.md +0 -480
- package/docs/patterns.md +0 -415
- package/docs/reserved.md +0 -7
- package/docs/tokens.md +0 -455
- package/docs/ui.md +0 -937
- package/docs/utilities.md +0 -384
- package/src/css/dialog-fallback.css +0 -56
- package/src/js/dialog-fallback.js +0 -163
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.otp { --otp-cell-size: var(--control-size); --otp-gap: var(--space-10); --otp-font-size: 1.5rem; --otp-cell-border: var(--border); --otp-cell-bg: var(--surface); position: relative; display: inline-grid; grid-auto-flow: column; grid-auto-columns: var(--otp-cell-size); inline-size: fit-content; max-inline-size: 100%; gap: var(--otp-gap); border-radius: var(--radius); direction: ltr; } .otp > span { inline-size: var(--otp-cell-size); block-size: var(--otp-cell-size); border: var(--border-width) solid var(--otp-cell-border); border-radius: var(--radius); background: var(--otp-cell-bg); transition: border-color var(--duration), background-color var(--duration), box-shadow var(--duration); } .otp > input { position: absolute; z-index: 1; inset: 0; inline-size: 100%; block-size: 100%; margin: 0; padding: 0 calc((var(--otp-cell-size) - 1ch) / 2); border: 0; outline: 2px solid transparent; box-shadow: none; background: transparent; color: var(--text); font-family: var(--font-mono); font-size: var(--otp-font-size); font-variant-numeric: tabular-nums; line-height: var(--otp-cell-size); letter-spacing: calc(var(--otp-cell-size) + var(--otp-gap) - 1ch); text-align: start; appearance: none; } .otp:focus-within { box-shadow: var(--focus-ring-shadow); } .otp > input:focus ~ span { --otp-cell-border: var(--focus); } .otp > input[aria-invalid="true"] ~ span, .needs-validation.was-validated .otp > input:invalid ~ span { --otp-cell-border: var(--danger); } @supports selector(:user-invalid) { .needs-validation .otp > input:user-invalid ~ span { --otp-cell-border: var(--danger); } } .otp > input:disabled { color: var(--state-disabled); cursor: not-allowed; } .otp > input:disabled ~ span { --otp-cell-bg: var(--surface-subtle); opacity: var(--disabled-opacity); } @media (forced-colors: active) { .otp:focus-within { outline: var(--focus-outline); outline-offset: var(--focus-outline-offset); box-shadow: none; } .otp > input:focus ~ span { --otp-cell-border: Highlight; } .otp > input[aria-invalid="true"] ~ span, .needs-validation.was-validated .otp > input:invalid ~ span { --otp-cell-border: Mark; } } .chat { --chat-gap: var(--space-30); --chat-max-size: min(85%, 40rem); display: grid; grid-auto-rows: min-content; column-gap: var(--chat-gap); } .chat-start { grid-template-columns: auto minmax(0, 1fr); justify-items: start; } .chat-end { grid-template-columns: minmax(0, 1fr) auto; justify-items: end; } .chat-avatar { grid-row: 1 / span 3; align-self: end; } .chat-start > .chat-avatar { grid-column: 1; } .chat-end > .chat-avatar { grid-column: 2; } .chat-header, .chat-footer { display: flex; gap: var(--space-10); color: var(--text-muted); font-size: var(--font-size-xs); } .chat-start > :is(.chat-header, .chat-bubble, .chat-footer) { grid-column: 2; } .chat-end > :is(.chat-header, .chat-bubble, .chat-footer) { grid-column: 1; } :where(.chat-bubble) { --intent: initial; --intent-fg: initial; --ui-bg: initial; --ui-fg: initial; --ui-border: initial; } .chat-bubble { --chat-bubble-bg: var(--ui-bg, var(--intent, var(--surface-subtle))); --chat-bubble-fg: var(--ui-fg, var(--intent-fg, var(--text))); --chat-bubble-border: var(--ui-border, transparent); inline-size: fit-content; max-inline-size: var(--chat-max-size); padding: var(--space-20) var(--space-30); border: var(--border-width) solid var(--chat-bubble-border); border-radius: var(--radius-lg); background: var(--chat-bubble-bg); color: var(--chat-bubble-fg); overflow-wrap: anywhere; } .chat-start > .chat-bubble { border-end-start-radius: var(--radius-sm); } .chat-end > .chat-bubble { border-end-end-radius: var(--radius-sm); } @supports selector(:has(*)) { .chat:not(:has(> .chat-avatar)) { column-gap: 0; } } @media (forced-colors: active) { .chat-bubble { border-color: CanvasText; } } @property --aura-angle { syntax: "<angle>"; inherits: false; initial-value: 0deg; } :where(.aura) { --intent: initial; --intent-fg: initial; } .aura { --aura-color: var(--intent, var(--primary)); --aura-width: 2px; --aura-blur: 0.75rem; --aura-radius: var(--radius-lg); --aura-duration: 6s; position: relative; isolation: isolate; display: inline-grid; padding: var(--aura-width); border-radius: calc(var(--aura-radius) + var(--aura-width)); background: var(--aura-color); background: conic-gradient(from var(--aura-angle), transparent 0 65%, var(--aura-color)); } .aura::before { position: absolute; z-index: -1; inset: 0; border-radius: inherit; background: inherit; filter: blur(var(--aura-blur)); opacity: 0.45; pointer-events: none; content: ""; } .aura > * { position: relative; } .aura-glow { background: var(--aura-color); } @media (prefers-reduced-motion: no-preference) { .aura:not(.aura-glow) { animation: aura-rotate var(--aura-duration) linear infinite; } } @keyframes aura-rotate { to { --aura-angle: 360deg; } } @supports selector(:has(*)) { .aura:has(> .btn) { --aura-radius: var(--radius); } .aura:has(> :is(.card, .alert)) { --aura-radius: var(--radius-lg); } .aura:has(> .badge) { --aura-radius: var(--radius-full); } } @media (forced-colors: active) { .aura { background: CanvasText; } .aura::before { display: none; } } .fab { --fab-offset: var(--space-40); --fab-gap: var(--space-20); position: fixed; z-index: var(--z-menu); inset-block-end: max(var(--fab-offset), env(safe-area-inset-bottom)); inset-inline-end: var(--fab-offset); } .fab > summary { list-style: none; cursor: pointer; } .fab > summary::-webkit-details-marker { display: none; } .fab > summary > [aria-hidden="true"]:only-child { display: inline-block; transition: transform var(--duration); } .fab-actions { position: absolute; inset-inline-end: 0; inset-block-end: calc(100% + var(--fab-gap)); display: flex; flex-direction: column; align-items: flex-end; gap: var(--fab-gap); opacity: 0; visibility: hidden; pointer-events: none; transform: translateY(var(--space-10)); transition: opacity var(--duration), transform var(--duration), visibility 0s linear var(--duration); } .fab-action { display: flex; align-items: center; justify-content: flex-end; gap: var(--fab-gap); inline-size: max-content; max-inline-size: calc(var(--viewport-inline) - var(--fab-offset) - var(--fab-offset)); } .fab-label { flex: 0 1 auto; overflow: hidden; max-inline-size: 16rem; padding: var(--space-10) var(--space-20); border-radius: var(--radius); background: var(--surface); color: var(--text); box-shadow: var(--shadow); font-size: var(--font-size-xs); font-weight: var(--font-weight-medium); line-height: var(--line-height-tight); text-overflow: ellipsis; white-space: nowrap; } .fab[open] > .fab-actions { opacity: 1; visibility: visible; pointer-events: auto; transform: translateY(0); transition-delay: 0s; } .fab[open] > summary > [aria-hidden="true"]:only-child { transform: rotate(45deg); } @media (forced-colors: active) { .fab-label { border: var(--border-width) solid CanvasText; box-shadow: none; } } @media print { .fab { display: none; } } .floating-field { --floating-pad-block-start: calc(var(--control-size) / 2 + var(--space-20)); position: relative; display: grid; } .floating-field > :is(.input, .textarea, .select) { padding-block-start: var(--floating-pad-block-start); padding-block-end: var(--space-10); } .floating-field > .field-label { position: absolute; inset-block-start: 50%; inset-inline-start: calc(var(--space-30) + var(--border-width)); transform: translateY(-50%); color: var(--text-muted); font-size: var(--control-font-size); font-weight: var(--font-weight); pointer-events: none; transition: inset-block-start var(--duration), transform var(--duration), font-size var(--duration), color var(--duration); } .floating-field > .textarea + .field-label { inset-block-start: var(--floating-pad-block-start); transform: none; } .floating-field > :is(.input, .textarea):is(:focus, :autofill, :not(:placeholder-shown)) + .field-label { inset-block-start: var(--space-10); transform: none; font-size: var(--font-size-xs); } .floating-field > :is( .select, .input[type="date"], .input[type="time"], .input[type="datetime-local"], .input[type="month"], .input[type="week"] ) + .field-label { inset-block-start: var(--space-10); transform: none; font-size: var(--font-size-xs); } .scroller { --scroller-size: 0.625rem; --scroller-padding: 0.125rem; --scroller-track: transparent; --scroller-thumb: var(--border); --scroller-thumb-hover: var(--text-muted); scrollbar-color: var(--scroller-thumb) var(--scroller-track); scrollbar-width: thin; } .scroller.stable-gutter { scrollbar-gutter: stable; } .scroller::-webkit-scrollbar { width: var(--scroller-size); height: var(--scroller-size); } .scroller::-webkit-scrollbar-track { background: var(--scroller-track); } .scroller::-webkit-scrollbar-thumb { border: var(--scroller-padding) solid transparent; border-radius: var(--radius-full); background: var(--scroller-thumb); background-clip: content-box; } .scroller::-webkit-scrollbar-thumb:hover { background: var(--scroller-thumb-hover); background-clip: content-box; } .scroller::-webkit-scrollbar-corner { background: var(--scroller-track); } @supports (color: color-mix(in oklch, red, white)) { .scroller { --scroller-thumb: color-mix(in oklch, var(--text-muted), transparent 55%); --scroller-thumb-hover: color-mix(in oklch, var(--text-muted), transparent 30%); } } .scroll-snap { --scroll-snap-gap: var(--gap); --scroll-snap-align: start; --scroll-snap-item-size: auto; display: flex; gap: var(--scroll-snap-gap); overflow-x: auto; } .scroll-snap > * { flex: 0 0 var(--scroll-snap-item-size); } @supports (scroll-snap-type: x mandatory) { .scroll-snap { overscroll-behavior-inline: contain; scroll-padding-inline: var(--scroll-snap-padding, var(--scroll-snap-gap)); scroll-snap-type: x mandatory; } .scroll-snap > * { scroll-snap-align: var(--scroll-snap-align); } .scroll-snap[data-snap="center"] { --scroll-snap-align: center; } } @supports (scroll-snap-type: inline mandatory) { .scroll-snap { scroll-snap-type: inline mandatory; } } .scroll-snap[data-scrollbar="hidden"] { scrollbar-width: none; } .scroll-snap[data-scrollbar="hidden"]::-webkit-scrollbar { display: none; } :root { --fluid-display: clamp(2.75rem, calc(2.25rem + 2.222vw), 4.25rem); --fluid-title: clamp(2rem, calc(1.75rem + 1.111vw), 2.75rem); --fluid-lead: clamp(1.125rem, calc(1.0833rem + 0.185vw), 1.25rem); } @supports (inline-size: 100vi) { :root { --fluid-display: clamp(2.75rem, calc(2.25rem + 2.222vi), 4.25rem); --fluid-title: clamp(2rem, calc(1.75rem + 1.111vi), 2.75rem); --fluid-lead: clamp(1.125rem, calc(1.0833rem + 0.185vi), 1.25rem); } } .text-display { font-size: var(--fluid-display); line-height: var(--line-height-tight); text-wrap: balance; } .text-title { font-size: var(--fluid-title); line-height: var(--line-height-tight); text-wrap: balance; } .text-lead { font-size: var(--fluid-lead); line-height: var(--line-height-relaxed); } .display-block { display: block; } .display-inline { display: inline; } .display-inline-block { display: inline-block; } .display-flex { display: flex; } .display-inline-flex { display: inline-flex; } .display-grid { display: grid; } .position-relative { position: relative; } .position-absolute { position: absolute; } .position-fixed { position: fixed; } .position-sticky { position: sticky; } .inline-size-full { inline-size: 100%; } .block-size-full { block-size: 100%; } .min-inline-size-0 { min-inline-size: 0; } .min-block-size-0 { min-block-size: 0; } .max-inline-size-none { max-inline-size: none; } .flex-wrap { flex-wrap: wrap; } .flex-nowrap { flex-wrap: nowrap; } .gap-sm { gap: var(--space-20); } .gap-md { gap: var(--space-40); } .gap-lg { gap: var(--space-50); } .justify-content-start { --cluster-justify: flex-start; justify-content: flex-start; } .justify-content-center { --cluster-justify: center; justify-content: center; } .justify-content-end { --cluster-justify: flex-end; justify-content: flex-end; } .justify-content-space-between { --cluster-justify: space-between; justify-content: space-between; } .align-self-start { align-self: start; } .align-self-center { align-self: center; } .align-self-end { align-self: end; } .padding-context { padding: var(--density-space); } .margin-inline-auto { margin-inline: auto; } .margin-inline-start-auto { margin-inline-start: auto; } .margin-inline-end-auto { margin-inline-end: auto; } .margin-block-start-auto { margin-block-start: auto; } .margin-block-end-auto { margin-block-end: auto; } .gap-context { gap: var(--gap); } .row-gap-context { row-gap: var(--gap); } .column-gap-context { column-gap: var(--gap); } .overflow-hidden { overflow: hidden; } .overflow-clip { overflow: clip; } .overflow-x-auto { overflow-x: auto; } .overflow-y-auto { overflow-y: auto; } .white-space-normal { white-space: normal; } .text-break { overflow-wrap: break-word; } .text-uppercase { text-transform: uppercase; } .text-lowercase { text-transform: lowercase; } .text-capitalize { text-transform: capitalize; } .font-weight-normal { font-weight: var(--font-weight); } .font-weight-medium { font-weight: var(--font-weight-medium); } .font-weight-strong { font-weight: var(--font-weight-strong); } .font-weight-bold { font-weight: var(--font-weight-bold); } .radius-sm-context { border-radius: var(--radius-sm); } .radius-context { border-radius: var(--radius); } .radius-lg-context { border-radius: var(--radius-lg); } .border-context { border: var(--border-width) solid var(--border); } .border-block-start { border-block-start: var(--border-width) solid var(--border); } .border-block-end { border-block-end: var(--border-width) solid var(--border); } .border-inline-start { border-inline-start: var(--border-width) solid var(--border); } .border-inline-end { border-inline-end: var(--border-width) solid var(--border); } .background-surface { background: var(--surface); color: var(--text); } .background-raised { background: var(--surface-raised); color: var(--text); } .background-subtle { background: var(--surface-subtle); color: var(--text); } .shadow-context { box-shadow: var(--shadow); } .shadow-popout-context { box-shadow: var(--shadow-popout); } .cursor-pointer { cursor: pointer; } .pointer-events-none { pointer-events: none; } .user-select-none { -webkit-user-select: none; user-select: none; } .display-none { display: none; } .visibility-hidden { visibility: hidden; } .switcher { display: flex; flex-wrap: wrap; gap: var(--gap); } .switcher > * { flex-basis: calc((var(--switcher-threshold, 40rem) - 100%) * 999); flex-grow: 1; min-inline-size: 0; } .topbar { position: sticky; inset-block-start: 0; z-index: var(--z-sticky); isolation: isolate; display: flex; align-items: center; gap: var(--gap); padding-block: var(--space-30); padding-inline: var(--space-40); border-block-end: var(--border-width) solid var(--border); background: var(--surface); } @supports ((backdrop-filter: blur(1rem)) or (-webkit-backdrop-filter: blur(1rem))) and (background: color-mix(in oklch, red 50%, transparent)) { .topbar { background: color-mix(in oklch, var(--surface) 88%, transparent); backdrop-filter: blur(0.75rem); -webkit-backdrop-filter: blur(0.75rem); } }
|
|
@@ -1,21 +1,94 @@
|
|
|
1
1
|
# Browser support strategy
|
|
2
2
|
|
|
3
|
+
## Actual tiers
|
|
4
|
+
|
|
5
|
+
The compatibility contract is expressed in browser versions, not ECMAScript
|
|
6
|
+
edition labels. Each tier is defined by what it guarantees.
|
|
7
|
+
|
|
8
|
+
| Actual tier | Firefox | Safari | Chromium | Contract |
|
|
9
|
+
| ------------ | ------: | --------: | -------: | ---------------------------------------------- |
|
|
10
|
+
| Degraded | 78+ | 14+ | 88+ | Semantic HTML + core CSS; JS unsupported |
|
|
11
|
+
| **Minimal** | **98+** | **15.4+** | **99+** | Full supported Actual experience, including JS |
|
|
12
|
+
| Intermediate | 121+ | 16+ | 106+ | More modern platform enhancements |
|
|
13
|
+
| Recommended | 129+ | 17.5+ | 123+ | Target experience |
|
|
14
|
+
|
|
15
|
+
- **Degraded** keeps useful HTML and CSS on older browsers. JavaScript is
|
|
16
|
+
explicitly outside the supported contract there.
|
|
17
|
+
- **Minimal** is the JavaScript baseline: the complete Actual experience,
|
|
18
|
+
including the runtime, is supported from this tier up. The runtime assumes
|
|
19
|
+
modern platform primitives available across the whole tier and ships no
|
|
20
|
+
legacy compatibility layer or polyfill.
|
|
21
|
+
|
|
22
|
+
Degraded browsers may still run some JavaScript successfully, but that
|
|
23
|
+
behavior is not tested, documented as supported, or preserved when the
|
|
24
|
+
runtime evolves.
|
|
25
|
+
|
|
26
|
+
## JavaScript philosophy
|
|
27
|
+
|
|
28
|
+
> The Degraded tier exists primarily to preserve useful HTML and CSS on older
|
|
29
|
+
> browsers. It does not constrain the JavaScript runtime.
|
|
30
|
+
|
|
31
|
+
> Minimal is the JavaScript baseline.
|
|
32
|
+
|
|
33
|
+
Modern JavaScript supported by all Minimal browsers may be used directly.
|
|
34
|
+
Actual does not transpile, polyfill, or wrap platform APIs solely to extend
|
|
35
|
+
JavaScript support below Minimal.
|
|
36
|
+
|
|
37
|
+
### Abortable listeners
|
|
38
|
+
|
|
39
|
+
Abortable event listeners predate the Minimal floor: Firefox 86, Chrome 88,
|
|
40
|
+
and WebKit (early 2021) all shipped the capability before the Safari 15.4
|
|
41
|
+
threshold. ([Bugzilla][1], [Chrome][2], [WebKit][3])
|
|
42
|
+
|
|
43
|
+
> `addEventListener({ signal })` is a baseline runtime primitive.
|
|
44
|
+
|
|
45
|
+
Do not introduce compatibility wrappers (`listen`, `on`,
|
|
46
|
+
`addAbortableListener`, `supportsSignal`) solely to reproduce a capability
|
|
47
|
+
that is already native throughout Minimal.
|
|
48
|
+
|
|
49
|
+
### Native `<dialog>`
|
|
50
|
+
|
|
51
|
+
The Minimal floor sits at the point where Safari 15.4 and Firefox 98 ship
|
|
52
|
+
native `<dialog>`, and Chromium already had it. ([WebKit][4])
|
|
53
|
+
|
|
54
|
+
> Native dialog is assumed by the runtime; no dialog polyfill is shipped.
|
|
55
|
+
|
|
56
|
+
### Future-direction invariants
|
|
57
|
+
|
|
58
|
+
1. **Minimal drives JavaScript compatibility; Degraded does not.**
|
|
59
|
+
2. **Prefer native platform APIs over compatibility abstractions when they are
|
|
60
|
+
available throughout Minimal.**
|
|
61
|
+
3. **Browser versions are the compatibility contract, not ECMAScript edition
|
|
62
|
+
labels.**
|
|
63
|
+
4. **Raise Minimal only when doing so buys a concrete simplification, removal
|
|
64
|
+
of a fallback, or materially better implementation.**
|
|
65
|
+
|
|
66
|
+
> Do not preserve an older JavaScript idiom solely because it happens to run
|
|
67
|
+
> in the Degraded tier. If the clearer native alternative is supported
|
|
68
|
+
> throughout Minimal, prefer it.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
3
72
|
## Philosophy
|
|
4
73
|
|
|
5
74
|
Works everywhere, works best on modern browsers. Wide browser support (even older browsers, not IE 11).
|
|
6
75
|
|
|
7
|
-
- No
|
|
76
|
+
- No consumer build step is required (just CSS).
|
|
8
77
|
- No post-processing — use only necessary browser-specific prefixes and engine hooks.
|
|
9
78
|
- CSS nesting is not used — code must be findable in the inspector. Rules are organized as if nesting were used, grouped together without nesting syntax. Exception: see [CSS nesting inside @supports](#css-nesting-inside-supports).
|
|
10
79
|
|
|
11
|
-
##
|
|
80
|
+
## Platform feature baseline
|
|
81
|
+
|
|
82
|
+
The tiers above are Actual's own contract. The table below tracks the Web
|
|
83
|
+
Platform Baseline of the individual CSS features the framework uses; it is a
|
|
84
|
+
different notion from an Actual tier.
|
|
12
85
|
|
|
13
86
|
| Feature area | Baseline | Progressive enhancement |
|
|
14
87
|
|------------------|-------------|-------------------------------|
|
|
15
88
|
| Layout (flex, grid, position) | Baseline 2023 | — |
|
|
16
89
|
| CSS custom properties | Baseline 2023 | — |
|
|
17
90
|
| `:has()` | Baseline 2023 | — |
|
|
18
|
-
| `<dialog>` element | Baseline 2023 |
|
|
91
|
+
| `<dialog>` element | Baseline 2023 | — |
|
|
19
92
|
| `color-mix()` | Baseline 2023 | Fallback flat color |
|
|
20
93
|
| `light-dark()` | 2024+ | Manual theme override |
|
|
21
94
|
| `:user-invalid` | 2024+ | `[aria-invalid]` attribute |
|
|
@@ -47,7 +120,7 @@ Modern features are gated with `@supports`; unsupported browsers keep the core l
|
|
|
47
120
|
|
|
48
121
|
### @supports usage
|
|
49
122
|
|
|
50
|
-
Use `@supports`
|
|
123
|
+
Use positive `@supports` by default. Use `@supports not` only for a genuine fallback branch that cannot safely coexist with the enhanced rules. Do not gate a single progressive declaration whose unsupported value is simply ignored by the browser, such as `text-wrap: balance`.
|
|
51
124
|
|
|
52
125
|
When a baseline declaration is already a valid fallback, prefer the normal cascade to an `@supports` wrapper. For example, older browsers keep the opaque surface here, while newer ones replace it with the translucent value:
|
|
53
126
|
|
|
@@ -75,7 +148,19 @@ Every `color-mix()` declaration needs a flat fallback outside `@supports` (older
|
|
|
75
148
|
|
|
76
149
|
### Hover rules
|
|
77
150
|
|
|
78
|
-
|
|
151
|
+
Use `:hover` directly for standard interactive feedback (links, buttons, table rows,
|
|
152
|
+
form controls, menu items, tabs, badges). The browser already knows when `:hover` can
|
|
153
|
+
apply; guarding it behind `@media (hover: hover)` needlessly suppresses hover on hybrid
|
|
154
|
+
devices whose primary pointer is touch but that also have a mouse or trackpad.
|
|
155
|
+
|
|
156
|
+
Reserve `@media (hover: hover)` only when hover:
|
|
157
|
+
|
|
158
|
+
- reveals controls or content,
|
|
159
|
+
- triggers heavy animation,
|
|
160
|
+
- changes layout,
|
|
161
|
+
- or is part of an interaction specifically designed for a mouse.
|
|
162
|
+
|
|
163
|
+
`:active` feedback stays unguarded.
|
|
79
164
|
|
|
80
165
|
### forced-colors
|
|
81
166
|
|
|
@@ -90,4 +175,8 @@ only through background color.
|
|
|
90
175
|
|
|
91
176
|
- Grouping works with `role="group"` and `:has()`: it is a progressive enhancement; UI should still be functional without it.
|
|
92
177
|
- Keep `:has()` out of selector lists that also contain legacy-safe selectors. Put the `:has()` branch in `@supports selector(...)` if necessary.
|
|
93
|
-
|
|
178
|
+
|
|
179
|
+
[1]: https://bugzilla.mozilla.org/show_bug.cgi?id=1679204 "1679204 - Consider to add signal to addEventListener"
|
|
180
|
+
[2]: https://developer.chrome.com/blog/new-in-chrome-88 "New in Chrome 88 | Blog"
|
|
181
|
+
[3]: https://trac.webkit.org/timeline "WebKit timeline, January 2021"
|
|
182
|
+
[4]: https://webkit.org/blog/12209/introducing-the-dialog-element/ "Introducing the Dialog Element"
|
|
@@ -21,7 +21,7 @@ Replace `actual.css` with `actual.layer.css`:
|
|
|
21
21
|
|
|
22
22
|
Author styles outside any layer will win over layer styles with equal specificity.
|
|
23
23
|
|
|
24
|
-
## Layer structure
|
|
24
|
+
## Layer structure
|
|
25
25
|
|
|
26
26
|
A single `actual` layer. The internal import order is a contract: later imports
|
|
27
27
|
can override earlier ones within the layer. Sublayers (`actual.reset`,
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Enhancement contract
|
|
2
|
+
|
|
3
|
+
Actual CSS splits CSS and JavaScript into two independent layers with a shared
|
|
4
|
+
opt-in mechanism. Every component uses the same grammar.
|
|
5
|
+
|
|
6
|
+
## The four-layer contract
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
class = presentation (.tabs, .flyout, .needs-validation)
|
|
10
|
+
data-enhance = JavaScript behavior (data-enhance="tabs")
|
|
11
|
+
ARIA / HTML = semantics (role="tab", aria-controls, hidden)
|
|
12
|
+
data-* = behavior configuration (data-mask, data-tooltip, data-filter)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The duplication is deliberate:
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<div class="tabs" data-enhance="tabs" role="tablist">
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- `.tabs` opts into the Actual CSS presentation;
|
|
22
|
+
- `data-enhance="tabs"` opts into the JavaScript behavior;
|
|
23
|
+
- `role="tablist"` declares semantics to assistive technology.
|
|
24
|
+
|
|
25
|
+
Each layer can be removed independently:
|
|
26
|
+
|
|
27
|
+
```html
|
|
28
|
+
<!-- CSS only: no JS behavior, just styling -->
|
|
29
|
+
<div class="tabs" role="tablist">…</div>
|
|
30
|
+
|
|
31
|
+
<!-- JS only: custom CSS, Actual behavior -->
|
|
32
|
+
<div class="my-tabs" data-enhance="tabs" role="tablist">…</div>
|
|
33
|
+
|
|
34
|
+
<!-- Both: standard Actual look and behavior -->
|
|
35
|
+
<div class="tabs" data-enhance="tabs" role="tablist">…</div>
|
|
36
|
+
|
|
37
|
+
<!-- Semantic only: author handles everything else -->
|
|
38
|
+
<div role="tablist">…</div>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## `data-enhance` grammar
|
|
42
|
+
|
|
43
|
+
Tokens are whitespace-separated, matched with the `~=` attribute selector, case-sensitive,
|
|
44
|
+
and must match `[a-z][a-z0-9-]*`. Order is irrelevant; duplicates are harmless.
|
|
45
|
+
|
|
46
|
+
```html
|
|
47
|
+
<form data-enhance="validation autosubmit"> <!-- two behaviors -->
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## When a token, when a `data-*`
|
|
51
|
+
|
|
52
|
+
> `data-enhance` is a generic opt-in for when HTML does not already provide an unambiguous one.
|
|
53
|
+
|
|
54
|
+
- **A token** is for a root controller — a behavior that owns a subtree and manages
|
|
55
|
+
descendants (tabs, flyout, scrollspy, validation).
|
|
56
|
+
- **A self-describing `data-*`** is for a leaf whose attribute is simultaneously the opt-in
|
|
57
|
+
and the configuration (`data-tooltip`, `data-mask`, `data-filter`,
|
|
58
|
+
`data-context-menu`).
|
|
59
|
+
- **Elements that are their own opt-in** need nothing (`<dialog>`).
|
|
60
|
+
|
|
61
|
+
`data-enhance` is explicitly **not** a mandatory prefix for every module.
|
|
62
|
+
|
|
63
|
+
## One registration per enhancement per root
|
|
64
|
+
|
|
65
|
+
`registerEnhancement()` owns a name per root. Registering the same token twice
|
|
66
|
+
on the same root throws; `disconnect()` releases the name; re-registration
|
|
67
|
+
after disconnect is allowed; the same token on a different root is independent.
|
|
68
|
+
|
|
69
|
+
`enhance()` stays generic and unguarded — registering the same selector twice
|
|
70
|
+
creates two independent records that both run.
|
|
71
|
+
|
|
72
|
+
The owner of a custom enhancement root must call `disconnect()` when disposing
|
|
73
|
+
that root; the default `document.documentElement` root is not affected.
|
|
74
|
+
|
|
75
|
+
## CSS must never select on `data-enhance`
|
|
76
|
+
|
|
77
|
+
CSS rules must not select on `data-enhance` or the runtime marker
|
|
78
|
+
`data-actual-surface`. Selecting on either would make the CSS API depend on the JS API,
|
|
79
|
+
defeating the split.
|
|
80
|
+
|
|
81
|
+
## Design decisions recorded
|
|
82
|
+
|
|
83
|
+
| # | Decision | Rationale |
|
|
84
|
+
|---|----------|-----------|
|
|
85
|
+
| D2 | `selectors.js` drops its 5 discovery entries, keeps 11 write-side state entries | The file's job changes from *CSS↔init bridge* to *state vocabulary the runtime writes*. It stays the single file to alias for another CSS framework. |
|
|
86
|
+
| D4 | `status.js` uses `[data-status][role="status"]` — no class, no token | The markup already carries `role="status"` everywhere. Semantics alone scope the document-wide lookup. |
|
|
87
|
+
| D5 | `<dialog>` gets no token | `<dialog>` is its own opt-in: semantic, native, CSS-agnostic. |
|
|
88
|
+
| D6 | `validation.js` accepts `[data-field-error]` as an alias for `.field-error` and keeps `.field` / `.danger` as an optional, degrading presentation adapter | Validation is the one module with intentional, optional CSS coupling. The adapter degrades to a no-op under foreign CSS while `aria-invalid`, focus management, custom rules, and server errors all keep working. |
|
|
89
|
+
| D7 | Context-menu reuses `surface`, not `flyout` | The panel gets no token; `menuFor()` validates semantically (`menu, [role="menu"]`). A context menu may legitimately want the flyout *look* without the flyout *behavior*. |
|
|
90
|
+
| D9 | Orphaned mounted surfaces closed from the origin's disconnect handler | A `fix:`, not part of the contract itself. Landed independently. |
|
|
91
|
+
|
|
92
|
+
## Token vocabulary
|
|
93
|
+
|
|
94
|
+
| Token | Behavior | Existing class |
|
|
95
|
+
|-------|----------|---------------|
|
|
96
|
+
| `tabs` | Tab panel switching, arrow-key navigation | `.tabs` |
|
|
97
|
+
| `flyout` | Trigger → panel positioning, open/close | `.flyout` |
|
|
98
|
+
| `scrollspy` | Scroll-driven nav highlighting | `.scrollspy` |
|
|
99
|
+
| `validation` | Form validation, focus management, server errors | `.needs-validation` |
|
|
100
|
+
|
|
101
|
+
Four tokens. Everything else stays self-describing.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Forced Colors (HCM) Strategy
|
|
2
|
+
|
|
3
|
+
## Principle: let the browser do the work
|
|
4
|
+
|
|
5
|
+
When `forced-colors: active`, the browser automatically remaps these properties to
|
|
6
|
+
the system palette:
|
|
7
|
+
|
|
8
|
+
- `border-color` → `ButtonText`
|
|
9
|
+
- `background` / `background-color` → `Canvas`
|
|
10
|
+
- `color` → `CanvasText`
|
|
11
|
+
|
|
12
|
+
This is the default behavior with `forced-color-adjust: auto` — and it applies to
|
|
13
|
+
**every** element, including pseudo-elements. Components do not need to redeclare
|
|
14
|
+
these properties in a forced-colors block.
|
|
15
|
+
|
|
16
|
+
## What the browser cannot understand
|
|
17
|
+
|
|
18
|
+
The browser sees `background-color`, `box-shadow`, `border-color`, `text-shadow`,
|
|
19
|
+
and non-URL `background-image` — it remaps or drops them all. It does **not**
|
|
20
|
+
understand:
|
|
21
|
+
|
|
22
|
+
- `mask-image` (SVG masks applied via `background-color`)
|
|
23
|
+
- Pseudo-elements whose `background-color` is used as a mask fill
|
|
24
|
+
- `box-shadow` used as a focus ring or shape indicator (the browser drops them)
|
|
25
|
+
- `background-image` carrying essential state or shape
|
|
26
|
+
|
|
27
|
+
These are the most common cases that need explicit forced-colors rules.
|
|
28
|
+
Add local overrides only when testing shows that browser remapping removes
|
|
29
|
+
an essential shape, state, or affordance.
|
|
30
|
+
|
|
31
|
+
## Pattern: custom property + single override
|
|
32
|
+
|
|
33
|
+
```css
|
|
34
|
+
.component {
|
|
35
|
+
--cmp-marker-color: var(--text-muted);
|
|
36
|
+
|
|
37
|
+
background: ...
|
|
38
|
+
color: ...
|
|
39
|
+
border: ...
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.component::marker {
|
|
43
|
+
background-color: var(--cmp-marker-color);
|
|
44
|
+
mask-image: url(...);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@media (forced-colors: active) {
|
|
48
|
+
.component {
|
|
49
|
+
--cmp-marker-color: CanvasText;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Color system keywords by role
|
|
55
|
+
|
|
56
|
+
| Context | Color | Why |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| Disclosure / accordion marker | `CanvasText` | Summary is on Canvas, not a button |
|
|
59
|
+
| Button, switch, checkbox | `ButtonText` / `ButtonFace` / `Highlight` / `HighlightText` | Interactive controls |
|
|
60
|
+
| Selected / current state | `Highlight` / `HighlightText` | Matches OS selection |
|
|
61
|
+
| Disabled state | `GrayText` | Standard disabled system color |
|
|
62
|
+
| Hover indicator | `ButtonText` outline or `CanvasText` underline | Avoids blocked backgrounds |
|
|
63
|
+
|
|
64
|
+
Prefer `CanvasText` for non-interactive mark-up content. `CanvasText` and
|
|
65
|
+
`ButtonText` are **not guaranteed identical** — custom HCM palettes can set
|
|
66
|
+
them differently.
|
|
67
|
+
|
|
68
|
+
## What to avoid
|
|
69
|
+
|
|
70
|
+
### `forced-color-adjust: none`
|
|
71
|
+
Only use when the color **must not** be remapped — e.g., a `background-color`
|
|
72
|
+
whose value is `currentColor` (the system would turn it into `Canvas` and make
|
|
73
|
+
the element invisible). Every `forced-color-adjust: none` blocks the browser
|
|
74
|
+
from fixing contrast issues automatically. Most components need zero instances.
|
|
75
|
+
|
|
76
|
+
### Redundant rules
|
|
77
|
+
```css
|
|
78
|
+
/* DON'T — the browser already does this */
|
|
79
|
+
@media (forced-colors: active) {
|
|
80
|
+
.component {
|
|
81
|
+
border-color: ButtonText;
|
|
82
|
+
background: Canvas;
|
|
83
|
+
color: CanvasText;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Redundant hover outlines
|
|
89
|
+
If the default focus is an outline on `:focus-visible`, it's already handled.
|
|
90
|
+
Hover doesn't need an outline duplicate. Reserve hover HCM rules for cases
|
|
91
|
+
where text-decoration (underline) is a better visual than an outline.
|
|
92
|
+
|
|
93
|
+
## Exemptions from forced-colors rules
|
|
94
|
+
|
|
95
|
+
Some components need **no** forced-color block at all because the default
|
|
96
|
+
remapping is perfect:
|
|
97
|
+
|
|
98
|
+
- `.card` — `background: Canvas`, `color: CanvasText` are already the defaults
|
|
99
|
+
- `.flyout` / `.menu` — `Canvas`/`CanvasText` for content, `ButtonText` for borders
|
|
100
|
+
- `.accordion` / `.breadcrumb` — pure remapping gets everything right
|
|
101
|
+
- `.status-bar` — `CanvasText` on `Canvas` background
|
|
102
|
+
|
|
103
|
+
The only forced-colors rules that should remain after a full audit are the ones
|
|
104
|
+
truly required by pseudo-element masks (`::after` / `::before` chevrons, custom
|
|
105
|
+
check/radio marks), non-native focus rings, and `currentColor` dependencies.
|
|
@@ -33,5 +33,7 @@ Actual CSS is intended for new projects. Existing projects with class collisions
|
|
|
33
33
|
- Prefer the shortest established term when it improves searchability without drifting from the component vocabulary (`.btn`, not `.button`).
|
|
34
34
|
- Classes name roles and boolean modifiers; `data-*` carries configurable values.
|
|
35
35
|
- `is-*` classes are runtime internals, not author API.
|
|
36
|
-
- Public JS data attributes are namespaced by feature (`data-flyout-*`, `data-context-menu-*`, `data-dialog-*`, `data-tooltip-*`; `data-toast-*` is reserved).
|
|
36
|
+
- Public JS data attributes are namespaced by feature (`data-flyout-*`, `data-context-menu-*`, `data-dialog-*`, `data-tooltip-*`; `data-toast-*` is reserved).
|
|
37
|
+
- Shared runtime primitives may have public JavaScript APIs without becoming author-facing CSS or HTML vocabulary. Do not introduce `.surface` or `data-surface-*`; components keep their feature-specific author APIs (`data-flyout-*`, `data-context-menu-*`, CSS `.flyout`, `.menu`, …).
|
|
38
|
+
- `data-enhance` is the one un-namespaced attribute — it carries *opt-in* for any feature, not *configuration* for one feature. Feature-namespaced attributes carry configuration.
|
|
37
39
|
- Custom events use the `actual:*` namespace.
|