@blankjs/react 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sezmol
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,190 @@
1
+ # blankjs
2
+
3
+ Form-first React components built on native form elements.
4
+
5
+ blankjs is a component library that treats the browser as an ally instead of an obstacle. Where other libraries rebuild form controls from `<div>`s and ARIA, blankjs styles the real elements and lets the platform do the work.
6
+
7
+ ## Packages
8
+
9
+ | Package | Contents |
10
+ |---------|----------|
11
+ | `@blankjs/react` | Styled, accessible components |
12
+ | `@blankjs/core` | Headless hooks: `useControllableState`, `useFieldControlProps`, `useCollection` |
13
+ | `@blankjs/tokens` | Design tokens as CSS variables |
14
+
15
+ ## Philosophy
16
+
17
+ ### Native first
18
+
19
+ Most libraries render a `<button role="checkbox">`, manage its state in JavaScript, and mirror the value into a hidden input for forms. blankjs renders a real `<input type="checkbox">` and strips its default look with `appearance: none`.
20
+
21
+ The browser then handles, with zero JavaScript on our side:
22
+
23
+ - **FormData serialization**: the value is in `new FormData(form)` because the input is real
24
+ - **Form reset**: `<button type="reset">` restores every control
25
+ - **Label activation**: clicking a `<label>` toggles the control
26
+ - **Radio semantics**: arrow-key navigation and roving tabindex come from the platform
27
+ - **Tab order**: one tab stop per radio group, as users expect
28
+
29
+ Checkbox, Switch, and RadioGroup are styled native inputs. Switch is a checkbox with `role="switch"`. No state mirroring, no synthetic focus management, no drift between what you see and what the form submits.
30
+
31
+ ### Form first, without a form library
32
+
33
+ Every component participates in a plain `<form>`:
34
+
35
+ ```tsx
36
+ <form onSubmit={handleSubmit}>
37
+ <Field.Root>
38
+ <Field.Label>Country</Field.Label>
39
+ <Select.Root name="country" defaultValue="AU">
40
+ <Select.Trigger>
41
+ <Select.Value placeholder="Select" />
42
+ </Select.Trigger>
43
+ <Select.Clear />
44
+ <Select.Content>
45
+ <Select.Item value="AU">Australia</Select.Item>
46
+ <Select.Item value="AT">Austria</Select.Item>
47
+ </Select.Content>
48
+ </Select.Root>
49
+ </Field.Root>
50
+
51
+ <MultiSelect.Root name="visited" defaultValue={["AU"]}>
52
+ {/* ... */}
53
+ </MultiSelect.Root>
54
+
55
+ <button type="submit">Submit</button>
56
+ <button type="reset">Reset</button>
57
+ </form>
58
+ ```
59
+
60
+ ```ts
61
+ const data = new FormData(form);
62
+ data.get("country"); // "AU"
63
+ data.getAll("visited"); // ["AU"]
64
+ ```
65
+
66
+ How it works:
67
+
68
+ - Composite widgets (Select, Combobox, MultiSelect) render hidden inputs bound to `name`
69
+ - MultiSelect submits one entry per selected value, read with `formData.getAll(name)`
70
+ - Every component listens for the form's `reset` event and restores its `defaultValue`, including controlled inputs the browser resets without firing a change event
71
+ - Disabled controls drop out of FormData, matching native behavior
72
+
73
+ No form library required. React Hook Form and friends still work if you want them, but they stop being a prerequisite.
74
+
75
+ ### A predictable event contract
76
+
77
+ Every component follows one rule: your handler runs first, then the library acts, unless you veto.
78
+
79
+ ```tsx
80
+ <Select.Item
81
+ value="delete-account"
82
+ onClick={(e) => {
83
+ if (!confirmed) e.preventDefault(); // selection blocked
84
+ }}
85
+ />
86
+ ```
87
+
88
+ This holds for every `onClick`, `onChange`, `onKeyDown`, and `onBlur` across the library. No `onSelect` vs `onClick` ambiguity, no undocumented ordering.
89
+
90
+ ### Composition over configuration
91
+
92
+ Compound components with real DOM parts you can style and rearrange:
93
+
94
+ ```tsx
95
+ <Combobox.Root name="city" inputValue={query} onInputValueChange={setQuery}>
96
+ <Combobox.Input />
97
+ <Combobox.Clear />
98
+ <Combobox.Content>
99
+ {cities.map((c) => (
100
+ <Combobox.Item key={c} value={c}>{c}</Combobox.Item>
101
+ ))}
102
+ </Combobox.Content>
103
+ </Combobox.Root>
104
+ ```
105
+
106
+ Filtering stays in your hands: you own the list, the library owns keyboard navigation, ARIA wiring, and commit semantics.
107
+
108
+ ## Components
109
+
110
+ | Component | Notes |
111
+ |-----------|-------|
112
+ | `Button` | Variants, native `type` handling |
113
+ | `TextInput` | Native input styled with tokens |
114
+ | `PasswordField` | Visibility toggle |
115
+ | `Textarea` | Auto-grows via CSS `field-sizing: content` |
116
+ | `Checkbox` | Native input, `indeterminate` support |
117
+ | `Switch` | Native checkbox with `role="switch"` |
118
+ | `RadioGroup` | Native radios, platform keyboard navigation |
119
+ | `Select` | Single value, typeahead, hidden input, `Select.Clear` |
120
+ | `MultiSelect` | Multiple values, stays open while picking, `formData.getAll` |
121
+ | `Combobox` | Controlled filtering, draft revert on Escape and blur |
122
+ | `Field` | Label, description, and error wiring through ARIA |
123
+
124
+ ### Field
125
+
126
+ `Field.Root` connects a control to its label, description, and error text:
127
+
128
+ ```tsx
129
+ <Field.Root invalid={!!error}>
130
+ <Field.Label>Email</Field.Label>
131
+ <TextInput name="email" />
132
+ <Field.Description>Work email preferred</Field.Description>
133
+ <Field.Error>{error}</Field.Error>
134
+ </Field.Root>
135
+ ```
136
+
137
+ The control receives `id`, `aria-describedby`, `aria-invalid`, and `disabled` from context. Group controls such as RadioGroup are labelled through `aria-labelledby`, since a `<div role="radiogroup">` is not a labelable element.
138
+
139
+ ## Theming
140
+
141
+ Tokens are CSS variables. Switch themes by setting one attribute, with no JavaScript involved:
142
+
143
+ ```html
144
+ <html data-bk-theme="dark">
145
+ ```
146
+
147
+ Override any token to rebrand:
148
+
149
+ ```css
150
+ :root {
151
+ --bk-color-accent: oklch(0.65 0.2 250);
152
+ }
153
+ ```
154
+
155
+ ## Usage
156
+
157
+ ```tsx
158
+ import { Select, Field, Checkbox } from "@blankjs/react";
159
+ import "@blankjs/react/styles.css";
160
+ ```
161
+
162
+ The packages are not published to npm yet. To explore locally:
163
+
164
+ ```bash
165
+ pnpm install
166
+ pnpm --filter playground dev
167
+ ```
168
+
169
+ The playground demonstrates every component plus a full form submitting through `FormData`.
170
+
171
+ ## Development
172
+
173
+ - pnpm workspaces + Turborepo
174
+ - React 19, TypeScript strict
175
+ - Vitest + Testing Library, 230+ tests across core and react
176
+ - tsdown for package builds
177
+
178
+ ```bash
179
+ pnpm test
180
+ pnpm typecheck
181
+ pnpm lint
182
+ ```
183
+
184
+ ## Status
185
+
186
+ Pre-release. The API may change before 1.0.
187
+
188
+ ## License
189
+
190
+ MIT