@monabbous/slidebox 1.2.0 → 1.3.1
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/README.md +358 -158
- package/dist/css/group.css +121 -0
- package/dist/css/slidebox.css +2 -0
- package/experimental/range.css +99 -0
- package/experimental/range.d.mts +14 -0
- package/experimental/range.mjs +213 -0
- package/less/experimental.less +119 -0
- package/less/slidebox.less +139 -0
- package/package.json +7 -3
- package/scss/_experimental.scss +109 -0
- package/scss/_mixins.scss +129 -0
- package/scss/group.scss +2 -0
- package/tailwind/experimental-rules.json +125 -0
- package/tailwind/group-rules.json +152 -0
- package/tailwind/index.cjs +8 -6
- package/tailwind/rules.json +2 -0
package/README.md
CHANGED
|
@@ -1,10 +1,85 @@
|
|
|
1
1
|
# Slidebox
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Native inputs. Just CSS.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Elastic checkboxes and grouped radios. Still real inputs: labels, keyboard controls, forms, and disabled states work as usual.
|
|
6
6
|
|
|
7
|
-
[
|
|
7
|
+
[](https://www.npmjs.com/package/@monabbous/slidebox)
|
|
8
|
+
[](https://www.npmjs.com/package/@monabbous/slidebox)
|
|
9
|
+
[](#tailwind)
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<img src="https://nabous.dev/project-assets/slidebox/assets/slidebox-demo.webp" width="520" alt="A Material-style native checkbox stretching and springing between checked and unchecked, using CSS">
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
The header demo is CSS-only: width `2.5`, spacing `−.27`, deform `.2`, and a `350ms` elastic easing. The thumb changes shape through margins, not scaling.
|
|
17
|
+
|
|
18
|
+
[Try the playground](https://nabous.dev/lab/slidebox/) · [Read the story](https://nabous.dev/blog/slidebox/) · [npm](https://www.npmjs.com/package/@monabbous/slidebox) · [Source](https://github.com/monabbous/slidebox)
|
|
19
|
+
|
|
20
|
+
Switches and radio groups need no JavaScript. The range slider is **experimental**; its optional motion and progress fill use JavaScript. Checkbox dragging is also optional and experimental.
|
|
21
|
+
|
|
22
|
+
## Contents
|
|
23
|
+
|
|
24
|
+
- [Demos](#demos)
|
|
25
|
+
- [Checkboxes](#checkbox-demos)
|
|
26
|
+
- [Radio group](#radio-group-demo)
|
|
27
|
+
- [Range](#range-demos-experimental)
|
|
28
|
+
- [Install](#install)
|
|
29
|
+
- [Vanilla HTML and CSS](#vanilla-html-and-css)
|
|
30
|
+
- [Checkbox presets](#checkbox-presets)
|
|
31
|
+
- [Grouped radio buttons](#grouped-radio-buttons)
|
|
32
|
+
- [Sass / SCSS](#sass--scss)
|
|
33
|
+
- [Less](#less)
|
|
34
|
+
- [Tailwind](#tailwind)
|
|
35
|
+
- [Defaults and controls](#defaults-and-controls)
|
|
36
|
+
- [Experimental checkbox dragging](#experimental-checkbox-dragging)
|
|
37
|
+
- [Experimental range slider](#experimental-range-slider)
|
|
38
|
+
- [Accessibility and browser support](#accessibility-and-browser-support)
|
|
39
|
+
- [Benchmarks](#benchmarks)
|
|
40
|
+
- [Development](#development)
|
|
41
|
+
|
|
42
|
+
## Demos
|
|
43
|
+
|
|
44
|
+
Click any preview to open the playground. These are crops of the actual recording, not rendered mockups.
|
|
45
|
+
|
|
46
|
+
### Checkbox demos
|
|
47
|
+
|
|
48
|
+
| Preset | nabous.dev | Browser colors | Width | Spacing | Deform |
|
|
49
|
+
| --- | --- | --- | --- | --- | --- |
|
|
50
|
+
| Default | [](https://nabous.dev/lab/slidebox/) | [](https://nabous.dev/lab/slidebox/) | 2 | .33 | .1 |
|
|
51
|
+
| Material UI | [](https://nabous.dev/lab/slidebox/) | [](https://nabous.dev/lab/slidebox/) | 2.5 | −.33 | .1 |
|
|
52
|
+
| Old iOS | [](https://nabous.dev/lab/slidebox/) | [](https://nabous.dev/lab/slidebox/) | 1.6 | .03 | 0 |
|
|
53
|
+
|
|
54
|
+
These are geometry presets, not framework integrations or exact replicas. The browser-color Material UI and Old iOS previews are stills; those two weren't toggled in the recording.
|
|
55
|
+
|
|
56
|
+
**Material UI geometry, elastic easing:**
|
|
57
|
+
|
|
58
|
+
[](https://nabous.dev/lab/slidebox/)
|
|
59
|
+
|
|
60
|
+
The header shows the larger custom variant. [Copy either configuration](#checkbox-presets).
|
|
61
|
+
|
|
62
|
+
### Radio group demo
|
|
63
|
+
|
|
64
|
+
[](https://nabous.dev/lab/slidebox/)
|
|
65
|
+
|
|
66
|
+
Week → Day → Week → Month. Pressing the active choice does nothing; selecting before or after it stretches the highlight in that direction. CSS only. [Radio group setup](#grouped-radio-buttons).
|
|
67
|
+
|
|
68
|
+
### Range demos (experimental)
|
|
69
|
+
|
|
70
|
+
**Native range, no JS enhancements** — an unfilled still reference:
|
|
71
|
+
|
|
72
|
+
[](https://nabous.dev/lab/slidebox/)
|
|
73
|
+
|
|
74
|
+
**Clicking to a value — motion and fill enabled (JS):**
|
|
75
|
+
|
|
76
|
+
[](https://nabous.dev/lab/slidebox/)
|
|
77
|
+
|
|
78
|
+
**Dragging — motion and fill enabled (JS):**
|
|
79
|
+
|
|
80
|
+
[](https://nabous.dev/lab/slidebox/)
|
|
81
|
+
|
|
82
|
+
The native control still owns its value. The helpers only add motion and fill. [Range setup and options](#experimental-range-slider).
|
|
8
83
|
|
|
9
84
|
## Install
|
|
10
85
|
|
|
@@ -12,286 +87,411 @@ An elastic CSS switch built on a native checkbox. No JavaScript, extra elements,
|
|
|
12
87
|
npm install @monabbous/slidebox
|
|
13
88
|
```
|
|
14
89
|
|
|
15
|
-
|
|
90
|
+
Pick one styling route: CSS, Sass, Less, or Tailwind. The mixins and plugin already include the component rules; don't import the plain CSS as well.
|
|
16
91
|
|
|
17
|
-
##
|
|
92
|
+
## Vanilla HTML and CSS
|
|
18
93
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
```js
|
|
22
|
-
import "@monabbous/slidebox/dist/css/slidebox.css";
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Add the class to a checkbox and give it an accessible label:
|
|
94
|
+
No bundler required:
|
|
26
95
|
|
|
27
96
|
```html
|
|
97
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@monabbous/slidebox/dist/css/slidebox.css">
|
|
98
|
+
|
|
28
99
|
<label>
|
|
29
|
-
<input class="slidebox" type="checkbox"
|
|
100
|
+
<input class="slidebox" type="checkbox" name="notifications">
|
|
30
101
|
Enable notifications
|
|
31
102
|
</label>
|
|
32
103
|
```
|
|
33
104
|
|
|
34
|
-
|
|
105
|
+
Pin the CDN URL to a package version in production. You can also copy `dist/css/slidebox.css` from the installed package and serve it yourself.
|
|
106
|
+
|
|
107
|
+
With a bundler, import the same stylesheet:
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
import "@monabbous/slidebox/dist/css/slidebox.css";
|
|
111
|
+
```
|
|
35
112
|
|
|
36
|
-
|
|
113
|
+
Use normal checkbox attributes: `checked`, `disabled`, `required`, `name`, and `value`. Add `role="switch"` if you want switch semantics instead of checkbox semantics. Always provide a label.
|
|
37
114
|
|
|
38
|
-
|
|
115
|
+
Set defaults on a parent and override individual inputs:
|
|
39
116
|
|
|
40
117
|
```css
|
|
41
118
|
:root {
|
|
42
119
|
color-scheme: light dark;
|
|
43
|
-
--slidebox-size:
|
|
44
|
-
--slidebox-on: #7c3aed;
|
|
120
|
+
--slidebox-size: 28px;
|
|
45
121
|
--slidebox-duration: 250ms;
|
|
46
122
|
}
|
|
47
123
|
|
|
48
124
|
.settings {
|
|
49
|
-
--slidebox-
|
|
125
|
+
--slidebox-on: #00b2b2;
|
|
50
126
|
}
|
|
51
127
|
|
|
52
|
-
.
|
|
128
|
+
.compact-switch {
|
|
53
129
|
--slidebox-size: 18px;
|
|
54
|
-
--slidebox-spacing: 0.25;
|
|
55
130
|
}
|
|
56
131
|
```
|
|
57
132
|
|
|
58
|
-
|
|
133
|
+
The package uses system colors by default. The recordings show the playground's **nabous.dev** glass skin alongside browser colors; that skin and its pointer lighting aren't applied to your app automatically.
|
|
59
134
|
|
|
60
|
-
##
|
|
135
|
+
## Checkbox presets
|
|
61
136
|
|
|
62
|
-
The
|
|
137
|
+
The [demo gallery](#checkbox-demos) shows each preset in **nabous.dev** and **browser colors**.
|
|
63
138
|
|
|
64
|
-
|
|
|
65
|
-
| --- | --- | --- |
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
139
|
+
| Preset | Width | Spacing | Deform |
|
|
140
|
+
| --- | --- | --- | --- |
|
|
141
|
+
| Default | 2 | .33 | .1 |
|
|
142
|
+
| Material UI | 2.5 | −.33 | .1 |
|
|
143
|
+
| Old iOS | 1.6 | .03 | 0 |
|
|
144
|
+
|
|
145
|
+
```css
|
|
146
|
+
.material-switch {
|
|
147
|
+
--slidebox-width: 2.5;
|
|
148
|
+
--slidebox-spacing: -.33;
|
|
149
|
+
--slidebox-deform: .1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.ios-switch {
|
|
153
|
+
--slidebox-width: 1.6;
|
|
154
|
+
--slidebox-spacing: .03;
|
|
155
|
+
--slidebox-deform: 0;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
76
158
|
|
|
77
|
-
|
|
159
|
+
Add these classes alongside `slidebox`.
|
|
78
160
|
|
|
79
|
-
|
|
161
|
+
### The elastic Material-style version
|
|
80
162
|
|
|
81
|
-
|
|
163
|
+
For the larger, slower version in the header, use this sampled `easeInOutElastic` curve:
|
|
164
|
+
|
|
165
|
+
```css
|
|
166
|
+
:root {
|
|
167
|
+
--ease-in-out-elastic: linear(
|
|
168
|
+
0, .0005, .001, .0011, .0003, -.0014, -.0037, -.0052,
|
|
169
|
+
-.0039, .0019, .012, .0221, .0239, .0077, -.0313, -.0831,
|
|
170
|
+
-.1175, -.0884, .0434, .2708, .5, .7292, .9566, 1.0884,
|
|
171
|
+
1.1175, 1.0831, 1.0313, .9923, .9761, .9779, .988, .9981,
|
|
172
|
+
1.0039, 1.0052, 1.0037, 1.0014, .9997, .9989, .999, .9995, 1
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.material-elastic {
|
|
177
|
+
--slidebox-width: 2.5;
|
|
178
|
+
--slidebox-spacing: -.27;
|
|
179
|
+
--slidebox-deform: .2;
|
|
180
|
+
--slidebox-duration: 350ms;
|
|
181
|
+
--slidebox-easing: var(--ease-in-out-elastic);
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```html
|
|
186
|
+
<label><input class="slidebox material-elastic" type="checkbox"> Enable sound</label>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The small preview above keeps the fixed Material UI preset (`−.33` spacing, `.1` deform, `200ms`); the header uses the custom values shown in the snippet. Both use CSS easing, not a JavaScript spring.
|
|
190
|
+
|
|
191
|
+
## Grouped radio buttons
|
|
192
|
+
|
|
193
|
+
Import the group stylesheet separately:
|
|
194
|
+
|
|
195
|
+
```html
|
|
196
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@monabbous/slidebox/dist/css/group.css">
|
|
197
|
+
|
|
198
|
+
<fieldset>
|
|
199
|
+
<legend>Calendar view</legend>
|
|
200
|
+
<div class="slidebox-group">
|
|
201
|
+
<label><input type="radio" name="view" value="day"><span>Day</span></label>
|
|
202
|
+
<label><input type="radio" name="view" value="week" checked><span>Week</span></label>
|
|
203
|
+
<label><input type="radio" name="view" value="month"><span>Month</span></label>
|
|
204
|
+
</div>
|
|
205
|
+
</fieldset>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Bundler import: `@monabbous/slidebox/dist/css/group.css`.
|
|
209
|
+
|
|
210
|
+
Keep the structure **group → label → input followed by span**. Radios in one group share a `name`; independent groups need different names. A `fieldset` and `legend` provide the group's accessible name.
|
|
211
|
+
|
|
212
|
+
Pressing the active segment doesn't deform it. Press an earlier or later segment and the current highlight stretches toward it. Labels stay put. Arrow keys and form submission remain native, including in RTL layouts.
|
|
213
|
+
|
|
214
|
+
Scoped CSS anchor positioning gives supported browsers a shared sliding highlight. Other modern browsers get a per-segment highlight. Neither version needs JavaScript. Keep labels short: segments share the available width in a single row. This is a radio group, not a tabs widget.
|
|
215
|
+
|
|
216
|
+
## Sass / SCSS
|
|
217
|
+
|
|
218
|
+
Use Dart Sass. Importing the mixin module emits no CSS until you include a mixin:
|
|
82
219
|
|
|
83
220
|
```scss
|
|
84
221
|
@use "@monabbous/slidebox/scss/mixins" as slidebox with (
|
|
85
|
-
$defaults: (
|
|
86
|
-
size: 24px,
|
|
87
|
-
on: #7c3aed,
|
|
88
|
-
duration: 250ms
|
|
89
|
-
)
|
|
222
|
+
$defaults: (on: #00b2b2, duration: 250ms)
|
|
90
223
|
);
|
|
91
224
|
|
|
92
|
-
.
|
|
93
|
-
@include slidebox.slidebox();
|
|
225
|
+
.switch {
|
|
226
|
+
@include slidebox.slidebox((size: 28px));
|
|
94
227
|
}
|
|
95
228
|
|
|
96
|
-
.
|
|
97
|
-
@include slidebox.slidebox((size:
|
|
229
|
+
.material-switch {
|
|
230
|
+
@include slidebox.slidebox((size: 28px, width: 2.5, spacing: -.33));
|
|
98
231
|
}
|
|
99
|
-
```
|
|
100
232
|
|
|
101
|
-
|
|
102
|
-
|
|
233
|
+
.view-picker {
|
|
234
|
+
@include slidebox.group((size: 36px, selected-text: #063c59));
|
|
235
|
+
}
|
|
103
236
|
```
|
|
104
237
|
|
|
105
|
-
|
|
238
|
+
Use your chosen class on the checkbox or group container instead of `slidebox` / `slidebox-group`. `$defaults` applies to both mixins; the map passed to an include overrides it. Configure the module once, before another module loads it.
|
|
239
|
+
|
|
240
|
+
For the ready-made classes, use `@use "@monabbous/slidebox/scss/slidebox";` and/or `@use "@monabbous/slidebox/scss/group";` instead. With the Sass CLI, add `--load-path=node_modules`.
|
|
106
241
|
|
|
107
|
-
|
|
242
|
+
The experimental range has a separate module:
|
|
108
243
|
|
|
109
244
|
```scss
|
|
110
|
-
@use "@monabbous/slidebox/scss/
|
|
245
|
+
@use "@monabbous/slidebox/scss/experimental" as experimental with (
|
|
246
|
+
$defaults: (on: #00b2b2, duration: 250ms)
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
.volume { @include experimental.range((size: 28px, spacing: .33)); }
|
|
111
250
|
```
|
|
112
251
|
|
|
113
|
-
## Less
|
|
252
|
+
## Less
|
|
114
253
|
|
|
115
|
-
Import the
|
|
254
|
+
Import the mixins, then create a wrapper for project defaults:
|
|
116
255
|
|
|
117
256
|
```less
|
|
118
257
|
@import "@monabbous/slidebox/less/slidebox.less";
|
|
119
258
|
|
|
120
|
-
.app-switch(@size:
|
|
121
|
-
.slidebox(@size: @size; @
|
|
259
|
+
.app-switch(@size: 28px; @width: 2; @spacing: .33) {
|
|
260
|
+
.slidebox(@size: @size; @width: @width; @spacing: @spacing; @on: #00b2b2; @duration: 250ms);
|
|
122
261
|
}
|
|
123
262
|
|
|
124
|
-
.
|
|
125
|
-
.
|
|
126
|
-
.
|
|
263
|
+
.switch { .app-switch(); }
|
|
264
|
+
.material-switch { .app-switch(@width: 2.5; @spacing: -.33); }
|
|
265
|
+
.small-switch { .app-switch(@size: 18px); }
|
|
266
|
+
.view-picker { .slidebox-group(@on: #00b2b2; @selected-text: #063c59); }
|
|
127
267
|
```
|
|
128
268
|
|
|
129
|
-
The import emits no CSS on its own. Use semicolons between arguments, especially
|
|
269
|
+
The import emits no CSS on its own. Use semicolons between arguments, especially with timing functions containing commas. With the Less CLI, add `--include-path=node_modules`.
|
|
130
270
|
|
|
131
|
-
|
|
271
|
+
For the experimental range:
|
|
132
272
|
|
|
133
|
-
|
|
273
|
+
```less
|
|
274
|
+
@import "@monabbous/slidebox/less/experimental.less";
|
|
134
275
|
|
|
135
|
-
|
|
276
|
+
.volume { .slidebox-range(@size: 28px; @spacing: .33; @on: #00b2b2); }
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Tailwind
|
|
136
280
|
|
|
137
|
-
|
|
281
|
+
The plugin is included in this package. Use your existing Tailwind setup; it supports **v3.4 and v4**.
|
|
282
|
+
|
|
283
|
+
### Tailwind v4
|
|
138
284
|
|
|
139
285
|
```css
|
|
140
286
|
@import "tailwindcss";
|
|
141
287
|
@plugin "@monabbous/slidebox/tailwind/index.cjs" {
|
|
142
|
-
size:
|
|
143
|
-
on: #
|
|
288
|
+
size: 28px;
|
|
289
|
+
on: #00b2b2;
|
|
144
290
|
duration: 250ms;
|
|
145
291
|
}
|
|
146
292
|
```
|
|
147
293
|
|
|
148
|
-
|
|
294
|
+
The options block sets project defaults. Omit it to keep the component defaults.
|
|
149
295
|
|
|
150
|
-
|
|
296
|
+
### Tailwind v3
|
|
151
297
|
|
|
152
298
|
```js
|
|
299
|
+
// tailwind.config.cjs
|
|
153
300
|
module.exports = {
|
|
154
301
|
content: ["./src/**/*.{html,js,ts,jsx,tsx,vue,svelte}"],
|
|
155
302
|
plugins: [
|
|
156
303
|
require("@monabbous/slidebox/tailwind/index.cjs")({
|
|
157
|
-
size: "
|
|
158
|
-
on: "#
|
|
304
|
+
size: "28px",
|
|
305
|
+
on: "#00b2b2",
|
|
159
306
|
duration: "250ms",
|
|
160
307
|
}),
|
|
161
308
|
],
|
|
162
309
|
};
|
|
163
310
|
```
|
|
164
311
|
|
|
165
|
-
###
|
|
166
|
-
|
|
167
|
-
Add `slidebox` for the component, then control it with `slidebox-NAME-[VALUE]` utilities:
|
|
312
|
+
### Classes
|
|
168
313
|
|
|
169
314
|
```html
|
|
170
315
|
<label>
|
|
171
|
-
<input
|
|
172
|
-
type="checkbox"
|
|
173
|
-
|
|
174
|
-
>
|
|
175
|
-
Enable notifications
|
|
316
|
+
<input class="slidebox slidebox-width-[2.5] slidebox-spacing-[-0.33] slidebox-on-teal-500"
|
|
317
|
+
type="checkbox">
|
|
318
|
+
Enable sound
|
|
176
319
|
</label>
|
|
320
|
+
|
|
321
|
+
<fieldset>
|
|
322
|
+
<legend>Calendar view</legend>
|
|
323
|
+
<div class="slidebox-group slidebox-selected-text-slate-950">
|
|
324
|
+
<label><input type="radio" name="view" value="day"><span>Day</span></label>
|
|
325
|
+
<label><input type="radio" name="view" value="week" checked><span>Week</span></label>
|
|
326
|
+
</div>
|
|
327
|
+
</fieldset>
|
|
328
|
+
|
|
329
|
+
<!-- Experimental range -->
|
|
330
|
+
<input class="slidebox-range w-64 slidebox-size-[28px]" type="range" aria-label="Volume">
|
|
177
331
|
```
|
|
178
332
|
|
|
179
|
-
|
|
180
|
-
- `track`, `border`, `off`, and `on` expose your Tailwind color palette, such as `slidebox-on-emerald-500`.
|
|
181
|
-
- `duration` and `easing` also expose Tailwind's transition theme, such as `slidebox-duration-300` and `slidebox-easing-linear`.
|
|
182
|
-
- Variants work normally: `md:slidebox-size-[32px]`, `hover:slidebox-on-emerald-600`, etc.
|
|
183
|
-
- Use `slidebox-size-*` rather than `h-*` to resize: thumb geometry uses the switch's font size.
|
|
184
|
-
- Keep complete class names in source; dynamically assembled class strings may not be detected by Tailwind.
|
|
333
|
+
No extra component stylesheet imports are needed. Classes are generated when used.
|
|
185
334
|
|
|
186
|
-
|
|
335
|
+
- Every control accepts arbitrary values: `slidebox-deform-[0.2]`, `slidebox-duration-[350ms]`, `slidebox-easing-[var(--ease-in-out-elastic)]`.
|
|
336
|
+
- Colors use your palette: `slidebox-on-teal-500`, `slidebox-track-slate-900`, `slidebox-text-white`, `slidebox-selected-text-slate-950`.
|
|
337
|
+
- Duration, easing, and radius also use the corresponding Tailwind themes: `slidebox-duration-300`, `slidebox-easing-linear`, `slidebox-radius-lg`.
|
|
338
|
+
- Variants work normally: `dark:slidebox-on-teal-300`, `md:slidebox-size-[36px]`.
|
|
339
|
+
- Use `slidebox-size-*` to resize a checkbox, not `h-*`. Its geometry follows font size. Ranges use normal width utilities such as `w-64`.
|
|
340
|
+
- Keep complete class names in source so Tailwind can detect them.
|
|
187
341
|
|
|
188
|
-
|
|
189
|
-
theme: {
|
|
190
|
-
extend: {
|
|
191
|
-
slidebox: {
|
|
192
|
-
size: { compact: "18px", comfortable: "28px" },
|
|
193
|
-
width: { wide: "2.5" },
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
// Now use slidebox-size-compact or slidebox-width-wide.
|
|
198
|
-
```
|
|
342
|
+
In v3, you can name your own values under `theme.extend.slidebox`, for example `size: { compact: "18px" }` gives you `slidebox-size-compact`.
|
|
199
343
|
|
|
200
|
-
|
|
344
|
+
## Defaults and controls
|
|
201
345
|
|
|
202
|
-
|
|
346
|
+
The same names work as `--slidebox-NAME` custom properties, Sass map keys, Less arguments (`@NAME`), and Tailwind plugin options/utilities.
|
|
203
347
|
|
|
204
|
-
|
|
348
|
+
| Control | Checkbox | Radio group | Experimental range |
|
|
349
|
+
| --- | --- | --- | --- |
|
|
350
|
+
| `size` | `54px` height | `36px` minimum segment height | `24px` track height |
|
|
351
|
+
| `width` | `2`, a width/height ratio greater than 1 | Use normal CSS width | Use normal CSS width |
|
|
352
|
+
| `spacing` | `.33`; negative values let the thumb protrude | `.12` padding/gap ratio | `.33` thumb/fill inset; negative values enlarge the thumb |
|
|
353
|
+
| `deform` | `.1` press deformation / delay ratio | `.06` directional press stretch | `.1` maximum travel lag / delay ratio; optional JS only |
|
|
354
|
+
| `duration` | `200ms` | `200ms` | `200ms` |
|
|
355
|
+
| `easing` | `ease-in-out` | `ease-in-out` | `ease-in-out` |
|
|
356
|
+
| `track` | `Field` | `Field` | `Field` |
|
|
357
|
+
| `border` | `ButtonBorder` | `ButtonBorder` | `ButtonBorder` |
|
|
358
|
+
| `off` | `FieldText` unchecked thumb | Unselected text fallback | Not used |
|
|
359
|
+
| `on` | Checked thumb color | Selected highlight color | Thumb and optional fill color |
|
|
360
|
+
| `text` | Not used | Unselected text; falls back to `off` / `FieldText` | Not used |
|
|
361
|
+
| `selected-text` | Not used | `HighlightText` | Not used |
|
|
362
|
+
| `radius` | Not used; pill shape | `100vw` | `100vw` |
|
|
205
363
|
|
|
206
|
-
|
|
364
|
+
`on` uses `AccentColor` where supported, otherwise `light-dark(#0b75ff, SelectedItem)`. Set `color-scheme: light dark` to follow the user's theme, or choose `light` / `dark` explicitly. When setting a custom radio highlight, set a contrasting `selected-text` too.
|
|
207
365
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
366
|
+
Keep spacing below `1`. Negative checkbox/range spacing is intentional; negative padding is clamped to zero for the native range. `deform: 0` removes deformation.
|
|
367
|
+
|
|
368
|
+
Inherited properties make good site defaults. A mixin/plugin default assigned directly to a control takes priority over inheritance; override that control with a selector or utility when needed.
|
|
369
|
+
|
|
370
|
+
## Experimental checkbox dragging
|
|
371
|
+
|
|
372
|
+
The CSS checkbox already handles clicks and keyboard input. This optional module lets its thumb follow a mouse, touch, or pen drag:
|
|
214
373
|
|
|
215
374
|
```js
|
|
216
|
-
import "@monabbous/slidebox/dist/css/slidebox.css"; // Omit for Sass, Less, or Tailwind.
|
|
217
375
|
import { enhanceSlidebox } from "@monabbous/slidebox/experimental/drag.mjs";
|
|
218
376
|
|
|
377
|
+
// Run after the input is mounted and styled with Slidebox.
|
|
219
378
|
const input = document.querySelector("#notifications");
|
|
220
379
|
const disableDragging = enhanceSlidebox(input, { threshold: 5 });
|
|
221
380
|
|
|
222
|
-
//
|
|
381
|
+
// On unmount, or when disabling the feature:
|
|
223
382
|
// disableDragging();
|
|
224
383
|
```
|
|
225
384
|
|
|
226
|
-
|
|
385
|
+
`threshold` is horizontal movement in CSS pixels before a drag starts; it defaults to `5`. Release at or beyond the midpoint to check the input. A changed state emits one bubbling `input` event, then one bubbling `change` event. The following pointer click is suppressed so it doesn't toggle twice.
|
|
227
386
|
|
|
228
|
-
|
|
387
|
+
Taps, label clicks, Space-key activation, disabled states, and form resets remain native. Vertical touch scrolling and pinch zoom stay available. Pointer cancellation abandons the gesture; RTL reverses its direction. `deform` and `duration` control the stretch; reduced motion disables it.
|
|
229
388
|
|
|
230
|
-
|
|
231
|
-
- The thumb follows the pointer; its trailing edge stretches and relaxes using `--slidebox-deform` and `--slidebox-duration`. Set `--slidebox-deform: 0` to disable stretching. Reduced-motion preferences also disable drag stretching.
|
|
232
|
-
- Releasing at or beyond the midpoint checks the input; releasing before it unchecks it. A changed state emits one bubbling `input` event followed by one bubbling `change` event. Drag movement alone does not emit either event.
|
|
233
|
-
- Normal clicks, label clicks, Space-key activation, form values, and form resets remain native. A completed drag suppresses its following click to prevent a second toggle. No synthetic `click` is emitted for a drag.
|
|
234
|
-
- RTL reverses the gesture direction. Disabled inputs, including those inside a disabled fieldset, cannot be dragged.
|
|
235
|
-
- Vertical touch gestures remain available for scrolling, and pinch zoom remains enabled. Cancellation or lost pointer capture abandons the gesture without committing it.
|
|
236
|
-
- Call the returned cleanup function to stop dragging and restore temporary styles. Cleanup can be called repeatedly. Disabling it during a drag cancels that drag.
|
|
237
|
-
- Repeated calls for the same input return its existing cleanup function. To change `threshold`, clean up and initialize again.
|
|
389
|
+
Calling the initializer again returns the existing cleanup function. Clean up before changing options or removing the input. Types are included, and importing the module is safe during server rendering; initialization needs a mounted checkbox and Pointer Events/pointer capture.
|
|
238
390
|
|
|
239
|
-
|
|
391
|
+
For controlled framework inputs, synchronize state from the native `input`/`change` events and detach those listeners on cleanup. Test that binding rather than assuming the framework's synthetic events behave identically.
|
|
240
392
|
|
|
241
|
-
|
|
242
|
-
import { useEffect, useRef, useState } from "react";
|
|
243
|
-
import { enhanceSlidebox } from "@monabbous/slidebox/experimental/drag.mjs";
|
|
393
|
+
## Experimental range slider
|
|
244
394
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
element.addEventListener("change", sync);
|
|
254
|
-
const destroy = enhanceSlidebox(element);
|
|
255
|
-
return () => {
|
|
256
|
-
destroy();
|
|
257
|
-
element.removeEventListener("change", sync);
|
|
258
|
-
};
|
|
259
|
-
}, []);
|
|
260
|
-
|
|
261
|
-
return (
|
|
262
|
-
<label>
|
|
263
|
-
<input ref={input} className="slidebox" type="checkbox" role="switch"
|
|
264
|
-
checked={enabled} onChange={event => setEnabled(event.target.checked)} />
|
|
265
|
-
Enable notifications
|
|
266
|
-
</label>
|
|
267
|
-
);
|
|
268
|
-
}
|
|
395
|
+
The unfilled native range works without JavaScript:
|
|
396
|
+
|
|
397
|
+
```html
|
|
398
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@monabbous/slidebox/experimental/range.css">
|
|
399
|
+
|
|
400
|
+
<label for="volume">Volume</label>
|
|
401
|
+
<input id="volume" class="slidebox-range" type="range"
|
|
402
|
+
name="volume" min="0" max="100" step="5" value="40">
|
|
269
403
|
```
|
|
270
404
|
|
|
271
|
-
|
|
405
|
+
Bundler import: `@monabbous/slidebox/experimental/range.css`. Sass, Less, and Tailwind equivalents are shown above.
|
|
272
406
|
|
|
273
|
-
|
|
407
|
+
Use normal `min`, `max`, `step`, `value`, and `disabled` attributes. Listen to `input` for live values or `change` for committed changes. Set the overall length with CSS width; `size` controls height. Thumb spacing applies at both endpoints.
|
|
274
408
|
|
|
275
|
-
|
|
409
|
+
### Optional motion and progress fill
|
|
410
|
+
|
|
411
|
+
The [range demos](#range-demos-experimental) show **both JavaScript options enabled**:
|
|
412
|
+
|
|
413
|
+
```js
|
|
414
|
+
import {
|
|
415
|
+
enhanceSlideboxRange,
|
|
416
|
+
enhanceSlideboxRangeFill,
|
|
417
|
+
} from "@monabbous/slidebox/experimental/range.mjs";
|
|
418
|
+
|
|
419
|
+
const input = document.querySelector("#volume");
|
|
420
|
+
const disableMotion = enhanceSlideboxRange(input);
|
|
421
|
+
const disableFill = enhanceSlideboxRangeFill(input);
|
|
422
|
+
|
|
423
|
+
// On unmount:
|
|
424
|
+
// disableMotion();
|
|
425
|
+
// disableFill();
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Use either helper alone or both together. Neither changes the native value or emits synthetic events.
|
|
429
|
+
|
|
430
|
+
**Motion:** the leading edge reaches the native destination; the tail follows after a delay. Reversing direction swaps the trailing edge. A temporary, non-interactive visual layer supplies the margin-based deformation without resizing the native hit box. It is removed after settling or cleanup. `deform` limits the lag and sets its initial delay (`duration × deform`); `duration` controls settling. Keyboard changes also animate. Reduced motion, forced colors, `deform: 0`, or `duration: 0ms` disables this effect.
|
|
431
|
+
|
|
432
|
+
**Fill:** a rounded, opaque progress bar follows the value, matching the thumb's height and inset. Progress is clamped to 0–100%, fill height is capped, and painting is clipped inside the track. RTL reverses it. It updates on `input`, `change`, and native form reset. After setting a value programmatically, dispatch an `input` event if you want the fill to update.
|
|
433
|
+
|
|
434
|
+
The helpers include TypeScript types. Repeated initialization returns the existing cleanup function. Use matching CSS and JS from the same package version, initialize after mounting, and clean up before removing the control. This is still experimental: horizontal sliders only, with more browser/device testing needed, particularly for transformed or zoomed containers and unusual stacking contexts.
|
|
435
|
+
|
|
436
|
+
In the playground, the range has its own section below the switch/radio controls. **Directional range motion (JS)** and **Fill progress (JS)** are independent and off by default. Reset turns both off.
|
|
437
|
+
|
|
438
|
+
## Accessibility and browser support
|
|
439
|
+
|
|
440
|
+
- Keep real labels. Use `fieldset` / `legend` for related radios.
|
|
441
|
+
- Native focus, keyboard interaction, validation, and form values stay in place. Don't remove focus outlines without replacing them.
|
|
442
|
+
- `prefers-reduced-motion` shortens CSS transitions and disables JS stretching. Forced-colors mode restores system colors and visible selection outlines.
|
|
443
|
+
- CSS uses logical margins for RTL. The optional helpers follow the same direction.
|
|
444
|
+
- Shared radio highlight motion needs scoped CSS anchor positioning; selection still works without it.
|
|
445
|
+
- Use a current browser with CSS custom properties, CSS math, logical properties, and `light-dark()` support. Native range styling and checkbox pseudo-elements can differ between engines.
|
|
446
|
+
|
|
447
|
+
The automated browser checks run in Chromium. Firefox/WebKit range rules are included, but those browsers and real touch devices still need testing. No claim that every control looks identical in every browser.
|
|
448
|
+
|
|
449
|
+
## Benchmarks
|
|
450
|
+
|
|
451
|
+
Slidebox is a small, configurable native-input implementation, not a claim to be the fastest switch. Other CSS-only approaches can produce similar deformation, and being free of a required JS runtime does not mean animating margins is free of layout work.
|
|
452
|
+
|
|
453
|
+
The source checkout includes a reproducible technique comparison:
|
|
276
454
|
|
|
277
455
|
```sh
|
|
278
456
|
npm ci
|
|
279
457
|
npm run build
|
|
280
|
-
|
|
281
|
-
npm
|
|
458
|
+
npx playwright install chromium
|
|
459
|
+
npm run benchmark -- --headed
|
|
282
460
|
```
|
|
283
461
|
|
|
284
|
-
|
|
462
|
+
It triggers 500 and 2,000 visible controls together, comparing margins, delayed edges, transform/grid slides, and an unanimated native reference. It tests property-specific `will-change` hints, saves raw data, and generates offline SVG graphs for callback cadence, layout/style work, and layout shifts. Optional tracing adds separate paint diagnostics.
|
|
285
463
|
|
|
286
|
-
|
|
464
|
+
**The cadence graphs measure rAF callbacks, not actual GPU-presented FPS.** The scenes are technique illustrations with different visual effects, not whole-library rankings. Read the [benchmark guide](benchmarks/README.md) for sources, options, warmup, limitations, and sharing results. Generated data stays out of Git and the npm package.
|
|
465
|
+
|
|
466
|
+
## Development
|
|
287
467
|
|
|
288
468
|
```sh
|
|
469
|
+
npm ci
|
|
470
|
+
npm run build
|
|
471
|
+
npm test
|
|
289
472
|
npx playwright install chromium
|
|
290
473
|
npm run test:browser
|
|
474
|
+
npm pack --dry-run
|
|
291
475
|
```
|
|
292
476
|
|
|
293
|
-
|
|
477
|
+
Serve the playground with `python3 -m http.server 8080`, then open `http://localhost:8080`.
|
|
478
|
+
|
|
479
|
+
The build extracts the switch from `style.css` and reads group/range rules from `components/`. It generates the package CSS, mixins, and Tailwind rules. Edit those sources, not the generated files.
|
|
480
|
+
|
|
481
|
+
The playground glass treatment follows [nabous.dev's current glass utilities](https://github.com/monabbous/nabous.dev/blob/master/src/styles/glassmorphism.css): bottom accent light, shared pointer glow, and masked borders. It stays out of the core package styles.
|
|
482
|
+
|
|
483
|
+
### Demo recordings
|
|
484
|
+
|
|
485
|
+
All demos are cropped from the same recording, with **lossless WebP encoding and original 60 fps timing**. No scaling, slowing down, or generated in-between frames. Static references are marked in [`assets/demos.json`](assets/demos.json), alongside exact crop rectangles, timestamps, and recorded settings.
|
|
486
|
+
|
|
487
|
+
With FFmpeg installed, rebuild them from the original file:
|
|
488
|
+
|
|
489
|
+
```sh
|
|
490
|
+
node scripts/build-demos.mjs /path/to/screenrecording-2026-10-31_05-12-41.mp4
|
|
491
|
+
# Or rebuild one clip:
|
|
492
|
+
node scripts/build-demos.mjs /path/to/screenrecording-2026-10-31_05-12-41.mp4 slidebox-demo.webp
|
|
493
|
+
```
|
|
294
494
|
|
|
295
|
-
Releases use the manual [Publish to npm](.github/workflows/publish-npm.yml) workflow on `main`, with
|
|
495
|
+
Releases use the manual [Publish to npm](.github/workflows/publish-npm.yml) workflow on `main`, with trusted publishing through `production`. Choose a version bump for a new release; use `publish_existing_version` only to retry a failed publish of the current version.
|
|
296
496
|
|
|
297
|
-
The original
|
|
497
|
+
The original library is preserved in [`legacy/`](legacy/).
|