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.
Files changed (215) hide show
  1. package/CHANGELOG.md +90 -1
  2. package/README.md +25 -8
  3. package/dist/actual-themes.min.css +1 -0
  4. package/dist/actual.css +1492 -931
  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 +95 -6
  10. package/docs/design-notes/cascade-layer.md +1 -1
  11. package/docs/design-notes/enhancement-contract.md +101 -0
  12. package/docs/design-notes/forced-colors.md +105 -0
  13. package/docs/design-notes/naming.md +3 -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 +119 -0
  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 +10 -4
  103. package/package.json +30 -16
  104. package/src/css/actual.css +12 -1
  105. package/src/css/base.css +3 -9
  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 +162 -49
  109. package/src/css/components/avatar.css +16 -10
  110. package/src/css/components/badge.css +82 -39
  111. package/src/css/components/breadcrumb.css +7 -15
  112. package/src/css/components/busy.css +7 -0
  113. package/src/css/components/button.css +82 -38
  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 -110
  117. package/src/css/components/index.css +1 -0
  118. package/src/css/components/join.css +14 -3
  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/scrollspy.css +2 -2
  128. package/src/css/components/skeleton.css +7 -0
  129. package/src/css/components/spinner.css +10 -11
  130. package/src/css/components/status-bar.css +18 -28
  131. package/src/css/components/surface.css +2 -8
  132. package/src/css/components/tab.css +20 -48
  133. package/src/css/components/table.css +7 -5
  134. package/src/css/components/tooltip.css +14 -7
  135. package/src/css/focus.css +16 -3
  136. package/src/css/forms/choice-card.css +13 -15
  137. package/src/css/forms/choice.css +21 -28
  138. package/src/css/forms/control.css +16 -26
  139. package/src/css/forms/custom-select.css +9 -11
  140. package/src/css/forms/form-actions.css +14 -3
  141. package/src/css/forms/form.css +7 -4
  142. package/src/css/forms/index.css +5 -4
  143. package/src/css/forms/input-icon.css +30 -12
  144. package/src/css/forms/native.css +48 -33
  145. package/src/css/forms/range.css +103 -0
  146. package/src/css/forms/select.css +8 -8
  147. package/src/css/forms/switch.css +14 -13
  148. package/src/css/forms/validation.css +32 -5
  149. package/src/css/grid.css +19 -18
  150. package/src/css/intents.css +2 -2
  151. package/src/css/layout.css +29 -1
  152. package/src/css/optional/aura.css +93 -0
  153. package/src/css/optional/chat.css +105 -0
  154. package/src/css/optional/fab.css +104 -0
  155. package/src/css/optional/floating-field.css +87 -0
  156. package/src/css/optional/index.css +5 -0
  157. package/src/css/optional/layout-extra.css +7 -4
  158. package/src/css/optional/otp.css +115 -0
  159. package/src/css/optional/scroll-snap.css +6 -0
  160. package/src/css/optional/scroller.css +19 -7
  161. package/src/css/optional/typography-fluid.css +5 -0
  162. package/src/css/optional/utilities-extra.css +17 -2
  163. package/src/css/prose.css +18 -8
  164. package/src/css/reset.css +0 -10
  165. package/src/css/theme.css +29 -13
  166. package/src/css/themes/cyberpunk.css +1 -1
  167. package/src/css/themes/dim.css +3 -3
  168. package/src/css/themes/edge.css +11 -37
  169. package/src/css/themes/index.css +3 -3
  170. package/src/css/themes/{dark.css → indigo.css} +5 -4
  171. package/src/css/themes/neon.css +5 -5
  172. package/src/css/themes/square.css +0 -3
  173. package/src/css/tokens.css +81 -21
  174. package/src/css/utilities.css +8 -10
  175. package/src/css/variants.css +37 -65
  176. package/src/js/command.js +1 -3
  177. package/src/js/context-menu.js +72 -53
  178. package/src/js/dialog.js +68 -79
  179. package/src/js/dismiss.js +15 -0
  180. package/src/js/enhance.js +76 -19
  181. package/src/js/enhancement-loader.js +301 -0
  182. package/src/js/events.js +3 -0
  183. package/src/js/filter.js +3 -3
  184. package/src/js/floating.js +101 -131
  185. package/src/js/flyout.js +106 -96
  186. package/src/js/focus-group.js +116 -0
  187. package/src/js/focus.js +6 -1
  188. package/src/js/index.js +4 -2
  189. package/src/js/input.js +1 -1
  190. package/src/js/keys.js +27 -0
  191. package/src/js/mask.js +7 -6
  192. package/src/js/menu.js +89 -35
  193. package/src/js/parse-config.js +47 -0
  194. package/src/js/password.js +9 -1
  195. package/src/js/scrollspy.js +133 -62
  196. package/src/js/selectors.js +14 -31
  197. package/src/js/status.js +16 -14
  198. package/src/js/surface.js +219 -125
  199. package/src/js/tab.js +46 -66
  200. package/src/js/tooltip.js +53 -31
  201. package/src/js/validation.js +45 -51
  202. package/dist/actual.js.map +0 -31
  203. package/docs/README.md +0 -26
  204. package/docs/components.md +0 -860
  205. package/docs/design-notes/previous-work.md +0 -5
  206. package/docs/forms.md +0 -1265
  207. package/docs/javascript.md +0 -426
  208. package/docs/layout.md +0 -480
  209. package/docs/patterns.md +0 -415
  210. package/docs/reserved.md +0 -7
  211. package/docs/tokens.md +0 -455
  212. package/docs/ui.md +0 -937
  213. package/docs/utilities.md +0 -384
  214. package/src/css/dialog-fallback.css +0 -56
  215. 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
 
@@ -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
- - Use `:has()` to improve simple structure, but do not turn it into a monolithic selector.
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 (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`,
@@ -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). 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`, …).
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.