opui-css 5.3.0 → 5.4.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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.4.0 - 2026-06-09
4
+
5
+ - Added global control for motion that lets you enable or disable all motion with a single class `.ui-motion-on`, `.ui-motion-off`, `.ui-motion-debug`. By default it will listen to `prefers-reduced-motion`. Locally each component individually has `--_motion` variable that can be changed as well.
6
+
3
7
  ## 5.3.0 - 2026-06-04
4
8
 
5
9
  Vue support! 🎉
@@ -11,6 +11,28 @@
11
11
  scrollbar-width: thin;
12
12
  }
13
13
 
14
+ html {
15
+ --motion: 1;
16
+ }
17
+
18
+ @media (prefers-reduced-motion: reduce) {
19
+ html {
20
+ --motion: 0;
21
+ }
22
+ }
23
+
24
+ .ui-motion-off {
25
+ --motion: 0 !important;
26
+ }
27
+
28
+ .ui-motion-on {
29
+ --motion: 1 !important;
30
+ }
31
+
32
+ .ui-motion-debug {
33
+ --motion: 10 !important;
34
+ }
35
+
14
36
  :where(html) {
15
37
  --_page-bg-color: var(--surface-default);
16
38
 
@@ -119,19 +141,11 @@
119
141
  border-radius: var(--border-radius, 0px);
120
142
  outline: 2px solid var(--_focus-visible-color);
121
143
  outline-offset: 2px;
122
- }
123
-
124
- @media (prefers-reduced-motion: no-preference) {
125
- :where(:focus-visible) {
126
- transition: outline-offset 0.15 var(--ease-2);
127
- }
128
-
129
- :where(:not(:active):focus-visible) {
130
- transition-duration: 0.15s;
131
- }
144
+ transition: outline-offset calc(0.15s * var(--motion)) var(--ease-2);
132
145
  }
133
146
 
134
147
  :where(:not(:active):focus-visible) {
148
+ transition-duration: calc(0.15s * var(--motion));
135
149
  outline-offset: var(--outline-offset, 0px);
136
150
  }
137
151
 
package/core/utils.css CHANGED
@@ -62,9 +62,7 @@ Add `.ui-scroll-lock` to any element that uses the [open] attribute (native <dia
62
62
  translateZ(-1px) scale(var(--thumb-scale));
63
63
  will-change: transform;
64
64
 
65
- @media (prefers-reduced-motion: no-preference) {
66
- transition: transform 0.2s ease;
67
- }
65
+ transition: transform calc(0.2s * var(--motion, 1)) ease;
68
66
  }
69
67
  }
70
68
  }
@@ -1,5 +1,6 @@
1
1
  @layer components.extended {
2
2
  :where(details.ui-accordion) {
3
+ --_motion: var(--motion, 1);
3
4
  --_actions-gap: var(--size-1);
4
5
  --_actions-margin-block-start: var(--size-3);
5
6
  --_actions-padding-block-end: var(--size-1);
@@ -15,8 +16,8 @@
15
16
  --_shadow: none;
16
17
  --_summary-padding-block: var(--size-3);
17
18
  --_summary-padding-inline: var(--_padding-inline);
18
- --_svg-transition: 0.2s var(--ease-1);
19
- --_transition: 0.2s;
19
+ --_svg-transition: calc(0.2s * var(--_motion)) var(--ease-1);
20
+ --_transition: calc(0.2s * var(--_motion));
20
21
 
21
22
  background-color: var(--_bg-color);
22
23
  border: var(--_border-width) solid var(--_border-color);
@@ -49,9 +50,7 @@
49
50
 
50
51
  /* Accordion animation */
51
52
  /* https://nerdy.dev/open-and-close-transitions-for-the-details-element */
52
- @media (prefers-reduced-motion: no-preference) {
53
- interpolate-size: allow-keywords;
54
- }
53
+ interpolate-size: allow-keywords;
55
54
 
56
55
  &::details-content {
57
56
  block-size: 0;
@@ -1,5 +1,6 @@
1
1
  @layer components.root {
2
2
  :where(.ui-button) {
3
+ --_motion: var(--motion, 1);
3
4
  --_color: light-dark(var(--color-16), var(--color-1));
4
5
  --_color-contrast: light-dark(var(--color-1), var(--color-16));
5
6
  --_color-tonal: var(--surface-tonal);
@@ -54,10 +55,10 @@
54
55
  text-align: center;
55
56
  text-decoration: none;
56
57
  transition:
57
- background-color 0.1s ease,
58
- color 0.1s ease,
59
- border-color 0.1s ease,
60
- box-shadow 0.1s ease;
58
+ background-color calc(0.1s * var(--_motion)) ease,
59
+ color calc(0.1s * var(--_motion)) ease,
60
+ border-color calc(0.1s * var(--_motion)) ease,
61
+ box-shadow calc(0.1s * var(--_motion)) ease;
61
62
  user-select: none;
62
63
 
63
64
  /* Colors — shared interaction model for critical and primary.
@@ -1,5 +1,6 @@
1
1
  @layer components.extended {
2
2
  :where(.ui-dialog) {
3
+ --_motion: var(--motion, 1);
3
4
  inline-size: 100%;
4
5
  inset: 0;
5
6
  margin: auto;
@@ -20,10 +21,6 @@
20
21
  &::backdrop {
21
22
  backdrop-filter: blur(1px);
22
23
  background-color: rgba(0, 0, 0, 0.5);
23
-
24
- @media (prefers-reduced-motion: reduce) {
25
- backdrop-filter: none;
26
- }
27
24
  }
28
25
 
29
26
  &:not([open]) {
@@ -42,22 +39,20 @@
42
39
  &[open] {
43
40
  opacity: 1;
44
41
  transition:
45
- display 0.2s allow-discrete,
46
- overlay 0.2s allow-discrete,
47
- opacity 0.2s var(--ease-out-1);
42
+ display calc(0.2s * var(--_motion)) allow-discrete,
43
+ overlay calc(0.2s * var(--_motion)) allow-discrete,
44
+ opacity calc(0.2s * var(--_motion)) var(--ease-out-1);
48
45
 
49
46
  @starting-style {
50
47
  opacity: 0;
51
48
  }
52
49
  }
53
50
 
54
- @media (prefers-reduced-motion: no-preference) {
55
- &[open] {
56
- margin-block-start: 15%;
51
+ &[open] {
52
+ margin-block-start: 15%;
57
53
 
58
- @starting-style {
59
- opacity: 0;
60
- }
54
+ @starting-style {
55
+ opacity: 0;
61
56
  }
62
57
  }
63
58
  }
@@ -1,5 +1,6 @@
1
1
  @layer components.root {
2
2
  :where(dialog.ui-drawer) {
3
+ --_motion: var(--motion, 1);
3
4
  --_backdrop-bg-color: transparent;
4
5
  --_backdrop-blur: blur(2px);
5
6
  --_bg-color: var(--surface-default);
@@ -14,7 +15,7 @@
14
15
  --_header-padding: var(--size-3);
15
16
  --_inline-size: min(375px, 100vi);
16
17
  --_shadow: var(--shadow-2);
17
- --_transition-duration: 0.2s;
18
+ --_transition-duration: calc(0.2s * var(--_motion));
18
19
  --_transition-timing: var(--ease-out-3);
19
20
 
20
21
  background-color: var(--_bg-color);
@@ -1,5 +1,6 @@
1
1
  @layer components.root {
2
2
  :where(.ui-icon-button) {
3
+ --_motion: var(--motion, 1);
3
4
  --_color: light-dark(var(--color-16), var(--color-1));
4
5
  --_color-contrast: light-dark(var(--color-1), var(--color-16));
5
6
  --_color-tonal: var(--surface-tonal);
@@ -44,10 +45,10 @@
44
45
  padding: 0;
45
46
  transform-style: preserve-3d;
46
47
  transition:
47
- background-color 0.1s ease,
48
- color 0.1s ease,
49
- border-color 0.1s ease,
50
- box-shadow 0.1s ease;
48
+ background-color calc(0.1s * var(--_motion)) ease,
49
+ color calc(0.1s * var(--_motion)) ease,
50
+ border-color calc(0.1s * var(--_motion)) ease,
51
+ box-shadow calc(0.1s * var(--_motion)) ease;
51
52
 
52
53
  &:where([disabled]) {
53
54
  --_text-color: light-dark(rgb(0, 0, 0/0.3), rgb(255, 255, 255/0.26));
@@ -67,12 +68,9 @@
67
68
  &::before {
68
69
  --highlight-size: 130%;
69
70
  background-color: transparent;
70
-
71
- @media (prefers-reduced-motion: no-preference) {
72
- transition:
73
- transform 0.2s ease,
74
- background-color 0.1s ease;
75
- }
71
+ transition:
72
+ transform calc(0.2s * var(--_motion)) ease,
73
+ background-color calc(0.1s * var(--_motion)) ease;
76
74
  }
77
75
 
78
76
  /* Colors — shared interaction model for critical and primary.
@@ -1,5 +1,6 @@
1
1
  @layer components.root {
2
2
  :where(.ui-progress) {
3
+ --_motion: var(--motion, 1);
3
4
  --_accent-color: var(--primary);
4
5
  --_bg-color: var(--surface-tonal);
5
6
 
@@ -40,11 +41,8 @@
40
41
 
41
42
  &[value]::-webkit-progress-value {
42
43
  background-color: var(--_accent-color);
43
-
44
- @media (prefers-reduced-motion: no-preference) {
45
- transition: inline-size 0.2s
46
- var(--ease-out-4, cubic-bezier(0, 0, 0.1, 1));
47
- }
44
+ transition: inline-size calc(0.2s * var(--_motion))
45
+ var(--ease-out-4, cubic-bezier(0, 0, 0.1, 1));
48
46
  }
49
47
 
50
48
  &::-moz-progress-bar {
@@ -53,33 +51,31 @@
53
51
  }
54
52
  }
55
53
 
56
- @media (prefers-reduced-motion: no-preference) {
57
- :where(.ui-progress):has(> progress:indeterminate) {
58
- &::after {
59
- animation: indeterminate 2s linear infinite;
60
- background-color: var(--_accent-color);
61
- content: "";
62
- inset: 0 auto 0 0;
63
- position: absolute;
64
- will-change: inset-inline-start, inset-inline-end;
65
- }
54
+ :where(.ui-progress):has(> progress:indeterminate) {
55
+ &::after {
56
+ animation: indeterminate calc(2s * var(--motion, 1)) linear infinite;
57
+ background-color: var(--_accent-color);
58
+ content: "";
59
+ inset: 0 auto 0 0;
60
+ position: absolute;
61
+ will-change: inset-inline-start, inset-inline-end;
62
+ }
66
63
 
67
- & > progress {
68
- &::-webkit-progress-value {
69
- background-color: transparent;
70
- }
64
+ & > progress {
65
+ &::-webkit-progress-value {
66
+ background-color: transparent;
67
+ }
71
68
 
72
- &::-moz-progress-bar {
73
- background-color: transparent;
74
- }
69
+ &::-moz-progress-bar {
70
+ background-color: transparent;
75
71
  }
76
72
  }
73
+ }
77
74
 
78
- [dir="rtl"] {
79
- :where(.ui-progress):has(> progress:indeterminate) {
80
- &::after {
81
- animation-direction: reverse;
82
- }
75
+ [dir="rtl"] {
76
+ :where(.ui-progress):has(> progress:indeterminate) {
77
+ &::after {
78
+ animation-direction: reverse;
83
79
  }
84
80
  }
85
81
  }
@@ -1,5 +1,6 @@
1
1
  @layer components.root {
2
2
  :where(.ui-range) {
3
+ --_motion: var(--motion, 1);
3
4
  display: grid;
4
5
  gap: var(--size-1) 0;
5
6
  /* Two-column base so the optional .value cell can sit next to the
@@ -273,9 +274,7 @@
273
274
  inline-size: var(--_thumb-size);
274
275
  margin-block-start: var(--_thumb-offset);
275
276
 
276
- @media (prefers-reduced-motion: no-preference) {
277
- transition: box-shadow 0.1s ease;
278
- }
277
+ transition: box-shadow calc(0.1s * var(--_motion)) ease;
279
278
 
280
279
  .ui-fieldset-item:focus-within & {
281
280
  border-color: var(--gray-14);
@@ -294,9 +293,7 @@
294
293
  inline-size: var(--_thumb-size);
295
294
  margin-block-start: var(--_thumb-offset);
296
295
 
297
- @media (prefers-reduced-motion: no-preference) {
298
- transition: box-shadow 0.1s ease;
299
- }
296
+ transition: box-shadow calc(0.1s * var(--_motion)) ease;
300
297
 
301
298
  .ui-fieldset-item:focus-within & {
302
299
  border-color: var(--gray-14);
@@ -1,5 +1,6 @@
1
1
  @layer components.extended {
2
2
  :where(.ui-select select) {
3
+ --_motion: var(--motion, 1);
3
4
  position: relative;
4
5
 
5
6
  /* Default arrow */
@@ -37,13 +38,11 @@
37
38
  transform: scale(0.9);
38
39
  }
39
40
 
40
- @media (prefers-reduced-motion: no-preference) {
41
- transition:
42
- display 0.2s allow-discrete,
43
- opacity 0.2s var(--ease-3),
44
- overlay 0.2s allow-discrete,
45
- scale 0.2s var(--ease-3);
46
- }
41
+ transition:
42
+ display calc(0.2s * var(--_motion)) allow-discrete,
43
+ opacity calc(0.2s * var(--_motion)) var(--ease-3),
44
+ overlay calc(0.2s * var(--_motion)) allow-discrete,
45
+ scale calc(0.2s * var(--_motion)) var(--ease-3);
47
46
  }
48
47
 
49
48
  /* Animation off-stage styles */
@@ -1,5 +1,6 @@
1
1
  @layer components.root {
2
2
  :where(.ui-switch) {
3
+ --_motion: var(--motion, 1);
3
4
  --_accent-color: var(--primary);
4
5
  --_accent-contrast: var(--primary-contrast);
5
6
 
@@ -12,7 +13,7 @@
12
13
  --_track-height: var(--size-5);
13
14
  --_track-width: var(--size-8);
14
15
  --_transition-tf: var(--ease-4);
15
- --_transition-time: 0.2s;
16
+ --_transition-time: calc(0.2s * var(--_motion));
16
17
 
17
18
  align-items: center;
18
19
  color: var(--text-primary);
@@ -84,28 +85,26 @@
84
85
  }
85
86
 
86
87
  /* Animation */
87
- @media (prefers-reduced-motion: no-preference) {
88
- /* Track */
89
- &::before {
90
- transition:
91
- background-color var(--_transition-time) var(--_transition-tf),
92
- border-color var(--_transition-time) var(--_transition-tf);
93
- }
88
+ /* Track */
89
+ &::before {
90
+ transition:
91
+ background-color var(--_transition-time) var(--_transition-tf),
92
+ border-color var(--_transition-time) var(--_transition-tf);
93
+ }
94
94
 
95
- /* Dot */
96
- &::after {
97
- transition: all var(--_transition-time) var(--_transition-tf);
98
- }
95
+ /* Dot */
96
+ &::after {
97
+ transition: all var(--_transition-time) var(--_transition-tf);
98
+ }
99
99
 
100
+ &:active:after {
101
+ --_dot-outline-size: calc(var(--size-1) + 1px);
102
+ }
103
+
104
+ &:checked {
100
105
  &:active:after {
101
106
  --_dot-outline-size: calc(var(--size-1) + 1px);
102
107
  }
103
-
104
- &:checked {
105
- &:active:after {
106
- --_dot-outline-size: calc(var(--size-1) + 1px);
107
- }
108
- }
109
108
  }
110
109
  }
111
110
 
@@ -4,6 +4,7 @@
4
4
  - Form related styles such as: label, supporting text, error handling
5
5
  */
6
6
  :where(.ui-text-field, .ui-textarea, .ui-select) {
7
+ --_motion: var(--motion, 1);
7
8
  --_accent-color: var(--primary);
8
9
  --_bg-color: var(--surface-default);
9
10
  --_border-color: var(--field-border-color);
@@ -33,9 +34,8 @@
33
34
  "footer footer footer";
34
35
  min-block-size: var(--_height);
35
36
 
36
- @media (prefers-reduced-motion: no-preference) {
37
- transition: border-color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
38
- }
37
+ transition: border-color calc(0.2s * var(--_motion))
38
+ cubic-bezier(0.4, 0, 0.2, 1);
39
39
  }
40
40
 
41
41
  :where(.ui-field) :where(input, textarea, select) {