actual-css 0.2.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.
Files changed (214) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/README.md +24 -9
  3. package/dist/actual-themes.min.css +1 -0
  4. package/dist/actual.css +1244 -915
  5. package/dist/actual.js +1 -3
  6. package/dist/actual.min.css +1 -1
  7. package/dist/optional.css +1048 -0
  8. package/dist/optional.min.css +1 -0
  9. package/docs/design-notes/browser-support.md +94 -14
  10. package/docs/design-notes/cascade-layer.md +1 -1
  11. package/docs/design-notes/enhancement-contract.md +11 -14
  12. package/docs/design-notes/forced-colors.md +105 -0
  13. package/docs/design-notes/naming.md +2 -1
  14. package/docs/design-notes/platform-alignment.md +492 -0
  15. package/docs/design-notes/references.md +8 -0
  16. package/docs/design-notes/widget-primitives.md +37 -1
  17. package/docs/navigation.json +135 -0
  18. package/docs/pages/components/accordion.md +80 -0
  19. package/docs/pages/components/alert.md +203 -0
  20. package/docs/pages/components/aura.md +35 -0
  21. package/docs/pages/components/avatar.md +95 -0
  22. package/docs/pages/components/badge.md +102 -0
  23. package/docs/pages/components/breadcrumb.md +19 -0
  24. package/docs/pages/components/button.md +205 -0
  25. package/docs/pages/components/card.md +183 -0
  26. package/docs/pages/components/chat.md +50 -0
  27. package/docs/pages/components/dialog.md +367 -0
  28. package/docs/pages/components/drawer.md +113 -0
  29. package/docs/pages/components/fab.md +118 -0
  30. package/docs/pages/components/key.md +33 -0
  31. package/docs/pages/components/meter.md +23 -0
  32. package/docs/pages/components/navbar.md +92 -0
  33. package/docs/pages/components/pagination.md +55 -0
  34. package/docs/pages/components/progress.md +29 -0
  35. package/docs/pages/components/skeleton.md +38 -0
  36. package/docs/pages/components/spinner.md +93 -0
  37. package/docs/pages/components/table.md +102 -0
  38. package/docs/pages/enhancements/flyout.md +391 -0
  39. package/docs/pages/enhancements/overview.md +36 -0
  40. package/docs/pages/enhancements/scrollspy.md +71 -0
  41. package/docs/pages/enhancements/status-bar.md +72 -0
  42. package/docs/pages/enhancements/tabs.md +80 -0
  43. package/docs/pages/enhancements/tooltip.md +130 -0
  44. package/docs/pages/examples/overview.md +41 -0
  45. package/docs/pages/forms/choice-cards.md +102 -0
  46. package/docs/pages/forms/controls.md +32 -0
  47. package/docs/pages/forms/detached-actions.md +59 -0
  48. package/docs/pages/forms/floating-field.md +88 -0
  49. package/docs/pages/forms/form-actions.md +56 -0
  50. package/docs/pages/forms/input-policy.md +91 -0
  51. package/docs/pages/forms/joined-controls.md +37 -0
  52. package/docs/pages/forms/labels.md +42 -0
  53. package/docs/pages/forms/otp.md +50 -0
  54. package/docs/pages/forms/overview.md +188 -0
  55. package/docs/pages/forms/password.md +40 -0
  56. package/docs/pages/forms/range.md +17 -0
  57. package/docs/pages/forms/states-and-sizes.md +321 -0
  58. package/docs/pages/forms/sticky-actions.md +187 -0
  59. package/docs/pages/forms/switch.md +31 -0
  60. package/docs/pages/forms/validation.md +241 -0
  61. package/docs/{mental-model.md → pages/foundations/philosophy.md} +33 -18
  62. package/docs/pages/foundations/theming.md +92 -0
  63. package/docs/pages/foundations/tokens.md +421 -0
  64. package/docs/{typography.md → pages/foundations/typography.md} +27 -9
  65. package/docs/pages/guides/bootstrap.md +438 -0
  66. package/docs/pages/guides/introduction.md +157 -0
  67. package/docs/pages/guides/optional-components.md +33 -0
  68. package/docs/pages/guides/progressive-enhancement.md +686 -0
  69. package/docs/pages/guides/tailwind.md +455 -0
  70. package/docs/pages/layout/app-shell.md +25 -0
  71. package/docs/pages/layout/center.md +29 -0
  72. package/docs/pages/layout/cluster.md +57 -0
  73. package/docs/pages/layout/frame.md +30 -0
  74. package/docs/pages/layout/grid.md +142 -0
  75. package/docs/pages/layout/list.md +41 -0
  76. package/docs/pages/layout/media.md +21 -0
  77. package/docs/pages/layout/overview.md +37 -0
  78. package/docs/pages/layout/scroll-snap.md +54 -0
  79. package/docs/pages/layout/sidebar-layout.md +27 -0
  80. package/docs/pages/layout/stack.md +18 -0
  81. package/docs/pages/layout/switcher.md +36 -0
  82. package/docs/pages/layout/topbar.md +23 -0
  83. package/docs/pages/patterns/actions.md +52 -0
  84. package/docs/pages/patterns/code-block.md +36 -0
  85. package/docs/pages/patterns/exclusive-accordion.md +16 -0
  86. package/docs/pages/patterns/hierarchical-nav.md +26 -0
  87. package/docs/pages/patterns/icon-input.md +25 -0
  88. package/docs/pages/patterns/intent-nesting.md +3 -0
  89. package/docs/pages/patterns/navigation-list.md +235 -0
  90. package/docs/pages/patterns/overview.md +58 -0
  91. package/docs/pages/patterns/structured-lists.md +47 -0
  92. package/docs/pages/utilities/accessibility.md +34 -0
  93. package/docs/pages/utilities/flex.md +105 -0
  94. package/docs/pages/utilities/links.md +18 -0
  95. package/docs/pages/utilities/list.md +31 -0
  96. package/docs/pages/utilities/overflow.md +43 -0
  97. package/docs/pages/utilities/overview.md +18 -0
  98. package/docs/pages/utilities/shape.md +25 -0
  99. package/docs/pages/utilities/sizing-wrapping.md +115 -0
  100. package/docs/pages/utilities/spacing.md +79 -0
  101. package/docs/pages/utilities/text.md +43 -0
  102. package/llms.txt +4 -4
  103. package/package.json +20 -12
  104. package/src/css/actual.css +12 -1
  105. package/src/css/base.css +3 -5
  106. package/src/css/components/accordion.css +18 -30
  107. package/src/css/components/actions.css +4 -1
  108. package/src/css/components/alert.css +126 -99
  109. package/src/css/components/avatar.css +16 -10
  110. package/src/css/components/badge.css +55 -35
  111. package/src/css/components/breadcrumb.css +7 -15
  112. package/src/css/components/busy.css +6 -0
  113. package/src/css/components/button.css +78 -32
  114. package/src/css/components/card.css +17 -37
  115. package/src/css/components/drawer.css +30 -13
  116. package/src/css/components/flyout.css +39 -109
  117. package/src/css/components/index.css +1 -0
  118. package/src/css/components/join.css +14 -2
  119. package/src/css/components/key.css +2 -0
  120. package/src/css/components/menu.css +125 -0
  121. package/src/css/components/meter.css +0 -23
  122. package/src/css/components/modal.css +162 -7
  123. package/src/css/components/navbar.css +17 -18
  124. package/src/css/components/overline.css +0 -7
  125. package/src/css/components/pagination.css +1 -1
  126. package/src/css/components/progress.css +8 -26
  127. package/src/css/components/skeleton.css +7 -0
  128. package/src/css/components/spinner.css +6 -7
  129. package/src/css/components/status-bar.css +15 -24
  130. package/src/css/components/surface.css +2 -8
  131. package/src/css/components/tab.css +19 -48
  132. package/src/css/components/table.css +7 -5
  133. package/src/css/components/tooltip.css +14 -7
  134. package/src/css/focus.css +14 -38
  135. package/src/css/forms/choice-card.css +13 -14
  136. package/src/css/forms/choice.css +21 -27
  137. package/src/css/forms/control.css +16 -17
  138. package/src/css/forms/custom-select.css +9 -11
  139. package/src/css/forms/form-actions.css +14 -3
  140. package/src/css/forms/form.css +7 -4
  141. package/src/css/forms/index.css +4 -4
  142. package/src/css/forms/input-icon.css +30 -12
  143. package/src/css/forms/native.css +50 -11
  144. package/src/css/forms/range.css +17 -9
  145. package/src/css/forms/select.css +8 -8
  146. package/src/css/forms/switch.css +14 -12
  147. package/src/css/forms/validation.css +32 -5
  148. package/src/css/grid.css +19 -18
  149. package/src/css/intents.css +2 -2
  150. package/src/css/layout.css +29 -1
  151. package/src/css/optional/aura.css +93 -0
  152. package/src/css/optional/chat.css +105 -0
  153. package/src/css/optional/fab.css +104 -0
  154. package/src/css/optional/floating-field.css +87 -0
  155. package/src/css/optional/index.css +5 -0
  156. package/src/css/optional/layout-extra.css +7 -4
  157. package/src/css/optional/otp.css +115 -0
  158. package/src/css/optional/scroll-snap.css +6 -0
  159. package/src/css/optional/scroller.css +13 -7
  160. package/src/css/optional/typography-fluid.css +5 -0
  161. package/src/css/optional/utilities-extra.css +17 -2
  162. package/src/css/prose.css +18 -8
  163. package/src/css/reset.css +0 -10
  164. package/src/css/theme.css +21 -5
  165. package/src/css/themes/cyberpunk.css +1 -1
  166. package/src/css/themes/dim.css +3 -3
  167. package/src/css/themes/edge.css +8 -10
  168. package/src/css/themes/index.css +3 -3
  169. package/src/css/themes/{dark.css → indigo.css} +5 -4
  170. package/src/css/themes/neon.css +5 -5
  171. package/src/css/themes/square.css +0 -3
  172. package/src/css/tokens.css +74 -20
  173. package/src/css/utilities.css +8 -10
  174. package/src/css/variants.css +37 -65
  175. package/src/js/command.js +1 -3
  176. package/src/js/context-menu.js +67 -35
  177. package/src/js/dialog.js +68 -79
  178. package/src/js/dismiss.js +15 -0
  179. package/src/js/enhance.js +54 -23
  180. package/src/js/enhancement-loader.js +301 -0
  181. package/src/js/events.js +3 -0
  182. package/src/js/filter.js +3 -3
  183. package/src/js/floating.js +101 -131
  184. package/src/js/flyout.js +104 -87
  185. package/src/js/focus-group.js +116 -0
  186. package/src/js/focus.js +6 -1
  187. package/src/js/index.js +4 -2
  188. package/src/js/input.js +1 -1
  189. package/src/js/keys.js +27 -0
  190. package/src/js/mask.js +7 -6
  191. package/src/js/menu.js +87 -34
  192. package/src/js/parse-config.js +47 -0
  193. package/src/js/password.js +9 -1
  194. package/src/js/scrollspy.js +12 -4
  195. package/src/js/selectors.js +0 -7
  196. package/src/js/status.js +3 -1
  197. package/src/js/surface.js +185 -136
  198. package/src/js/tab.js +36 -53
  199. package/src/js/tooltip.js +53 -31
  200. package/src/js/validation.js +34 -8
  201. package/dist/actual.js.map +0 -31
  202. package/docs/README.md +0 -28
  203. package/docs/components.md +0 -860
  204. package/docs/design-notes/previous-work.md +0 -5
  205. package/docs/forms.md +0 -1265
  206. package/docs/javascript.md +0 -496
  207. package/docs/layout.md +0 -480
  208. package/docs/patterns.md +0 -504
  209. package/docs/reserved.md +0 -7
  210. package/docs/tokens.md +0 -455
  211. package/docs/ui.md +0 -929
  212. package/docs/utilities.md +0 -384
  213. package/src/css/dialog-fallback.css +0 -56
  214. 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 compile or build step (just CSS).
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
- ## Baseline
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 | Below that: rudimentary shim (`dialog-fallback.js` + `dialog-fallback.css`, both standalone and omittable in custom builds), no top layer or focus trap |
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` positively (no `@supports not`) only when it protects a dependent group of rules or a real fallback/modern branch. Do not gate a single progressive declaration whose unsupported value is simply ignored by the browser, such as `text-wrap: balance`.
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
- Hover rules that change surface colors go inside `@media (hover: hover)`. `:active` feedback stays unguarded.
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
 
@@ -91,12 +176,7 @@ only through background color.
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
 
94
- ## Native-first audit (0.2)
95
-
96
- `dialog.js`, `surface.js`, and the drawer keep the modern path thin
97
- (`<dialog>` with `showModal` / `close`) and the fallback contained
98
- (`dialog-fallback.js` shim). The JS runtime's baseline is far below
99
- Chrome 130 / Safari 18: no `FocusTrap` or `ScrollBarHelper` shim deletion,
100
- no `@property { inherits: false }` for utility locals. Every native-feature
101
- removal must clear a stated floor — not follow a competitor's.
102
- - Use `:has()` to improve simple structure, but do not turn it into a monolithic selector.
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 (v0.1)
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`,
@@ -1,6 +1,6 @@
1
1
  # Enhancement contract
2
2
 
3
- Actual CSS 0.2 splits CSS and JavaScript into two independent layers with a shared
3
+ Actual CSS splits CSS and JavaScript into two independent layers with a shared
4
4
  opt-in mechanism. Every component uses the same grammar.
5
5
 
6
6
  ## The four-layer contract
@@ -62,9 +62,15 @@ and must match `[a-z][a-z0-9-]*`. Order is irrelevant; duplicates are harmless.
62
62
 
63
63
  ## One registration per enhancement per root
64
64
 
65
- No core mechanism prevents registering the same token twice on the same root; doing so
66
- creates two independent records that both run. This is documented behaviour, not guarded
67
- at runtime.
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.
68
74
 
69
75
  ## CSS must never select on `data-enhance`
70
76
 
@@ -83,16 +89,7 @@ defeating the split.
83
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*. |
84
90
  | D9 | Orphaned mounted surfaces closed from the origin's disconnect handler | A `fix:`, not part of the contract itself. Landed independently. |
85
91
 
86
- ### Accepted debt
87
-
88
- `flyout.js`'s trigger selector `[aria-controls][aria-expanded]` is the broadest selector
89
- in the runtime and matches unrelated application disclosures (`connectTrigger` bails
90
- afterwards via `flyoutFor()`). It is already ARIA-only and stays as-is:
91
-
92
- > Broad trigger discovery is intentional for dynamically inserted triggers.
93
- > Revisit only if profiling or interoperability shows a real problem.
94
-
95
- ## Token vocabulary (0.2.0)
92
+ ## Token vocabulary
96
93
 
97
94
  | Token | Behavior | Existing class |
98
95
  |-------|----------|---------------|
@@ -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,6 +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). Do not expose shared implementation names such as `surface` unless they are meant as a stable author concept.
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`, …).
37
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.
38
39
  - Custom events use the `actual:*` namespace.