@thinking.tools/teff 1.0.2 → 1.0.3
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 +6 -2
- package/REFERENCE.md +488 -0
- package/custom-elements.json +69 -0
- package/llms.txt +25 -0
- package/package.json +15 -2
- package/teff.d.ts +90 -0
package/README.md
CHANGED
|
@@ -24,8 +24,8 @@ npm install @thinking.tools/teff
|
|
|
24
24
|
|
|
25
25
|
```js
|
|
26
26
|
// once, in your app entry
|
|
27
|
-
import "teff/teff.min.css";
|
|
28
|
-
import "teff"; // optional: registers the custom elements + the `teff` global
|
|
27
|
+
import "@thinking.tools/teff/teff.min.css";
|
|
28
|
+
import "@thinking.tools/teff"; // optional: registers the custom elements + the `teff` global
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
## Use
|
|
@@ -51,6 +51,10 @@ The CSS covers native elements (buttons, forms, tables, `<dialog>`, `<details>`,
|
|
|
51
51
|
|
|
52
52
|
Open `index.html` for the full component gallery with copy-paste examples.
|
|
53
53
|
|
|
54
|
+
## Docs
|
|
55
|
+
|
|
56
|
+
[`REFERENCE.md`](REFERENCE.md) is the complete single-file reference — every component, token, and JS API with copy-paste HTML. It ships in the npm package alongside [`llms.txt`](llms.txt) (for AI coding agents), `teff.d.ts` (TypeScript declarations), and `custom-elements.json` (custom elements manifest).
|
|
57
|
+
|
|
54
58
|
## Theming
|
|
55
59
|
|
|
56
60
|
Flip presets on `<html>`, or override tokens directly:
|
package/REFERENCE.md
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
# teff — complete reference
|
|
2
|
+
|
|
3
|
+
> teff is an ultra-lightweight semantic UI library: one CSS file (~9 kB gzip) and one JS file (~3 kB gzip), independently usable. The CSS styles plain semantic HTML — no framework, no build step, no dependencies, dark mode built in. The JS adds two custom elements, a few automatic enhancements, and two imperative APIs on the `teff` global.
|
|
4
|
+
|
|
5
|
+
This file is the canonical, self-contained API reference. Every snippet is copy-paste ready.
|
|
6
|
+
|
|
7
|
+
- npm package: `@thinking.tools/teff`
|
|
8
|
+
- Source: https://codeberg.org/thinking_tools/teff
|
|
9
|
+
- Live gallery: https://thinking_tools.codeberg.page/teff/
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
CDN (no build step):
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<link rel="stylesheet" href="https://unpkg.com/@thinking.tools/teff/teff.min.css" />
|
|
17
|
+
<script src="https://unpkg.com/@thinking.tools/teff/teff.min.js"></script>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
npm + bundler:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install @thinking.tools/teff
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
// once, in your app entry
|
|
28
|
+
import "@thinking.tools/teff/teff.min.css";
|
|
29
|
+
import "@thinking.tools/teff"; // optional: registers the custom elements + the `teff` global
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The two files are independent: the CSS works without the JS (everything degrades to functional native HTML), and the JS works without the CSS.
|
|
33
|
+
|
|
34
|
+
## Core principles
|
|
35
|
+
|
|
36
|
+
1. **Semantic HTML first.** Write native elements (`<button>`, `<dialog>`, `<details>`, `<progress>`); they are styled out of the box. Class names and `data-*` attributes only refine.
|
|
37
|
+
2. **Variants via `data-variant`**, styles/sizes via classes (`.outline`, `.ghost`, `.small`, `.large`).
|
|
38
|
+
3. **JS enhances existing markup.** Custom elements (`<teff-tabs>`, `<teff-dropdown>`) wrap your own ARIA markup and wire state, keyboard navigation, and ARIA attributes. No Shadow DOM, no rendered markup.
|
|
39
|
+
4. **Everything themes through CSS custom properties** with `light-dark()` — never hardcode colors.
|
|
40
|
+
|
|
41
|
+
## Theming
|
|
42
|
+
|
|
43
|
+
Set presets as attributes on `<html>` (all optional, all combinable):
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<html data-theme="dark" data-accent="blue" data-radius="soft" data-density="compact">
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Attribute | Values | Default (attribute absent) |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `data-theme` | `light`, `dark` | follows OS (`color-scheme: light dark`) |
|
|
52
|
+
| `data-accent` | `blue`, `teal`, `green`, `orange`, `red`, `wine`, `navy` | mono ink (neutral black/white primary) |
|
|
53
|
+
| `data-radius` | `soft`, `sharp` | fully rounded (pill buttons/fields) |
|
|
54
|
+
| `data-density` | `compact`, `comfortable` | 1.0 spacing scale |
|
|
55
|
+
|
|
56
|
+
Or override tokens directly:
|
|
57
|
+
|
|
58
|
+
```css
|
|
59
|
+
:root {
|
|
60
|
+
--primary: light-dark(#2068c9, #7ab3ff); /* light value, dark value */
|
|
61
|
+
--radius-button: 0.75rem;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Design tokens (CSS custom properties on `:root`)
|
|
66
|
+
|
|
67
|
+
Colors — all defined with `light-dark(light, dark)`, so dark mode is automatic:
|
|
68
|
+
|
|
69
|
+
| Token | Purpose |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `--background` / `--foreground` | page surface / text |
|
|
72
|
+
| `--card` / `--card-foreground` | elevated surface / its text |
|
|
73
|
+
| `--primary` / `--primary-foreground` | primary action; changed by `data-accent` |
|
|
74
|
+
| `--secondary` / `--secondary-foreground` | secondary action |
|
|
75
|
+
| `--muted` / `--muted-foreground` | subdued surface / subdued text |
|
|
76
|
+
| `--faint` / `--faint-foreground` | faintest surface / faintest text |
|
|
77
|
+
| `--accent` | hover/highlight surface |
|
|
78
|
+
| `--danger` / `--danger-foreground` | destructive |
|
|
79
|
+
| `--success` / `--success-foreground` | positive |
|
|
80
|
+
| `--warning` / `--warning-foreground` | cautionary |
|
|
81
|
+
| `--border`, `--input`, `--ring` | hairline borders, input borders, focus ring |
|
|
82
|
+
| `--logo-plum`, `--logo-red`, `--logo-orange`, `--logo-citron`, `--logo-green`, `--logo-pine`, `--logo-navy`, `--logo-blue`, `--logo-sky` | raw 9-stop logo palette |
|
|
83
|
+
|
|
84
|
+
Spacing — `--space-1` (0.25rem) through `--space-2/3/4/5/6/8/10/12/14/16/18` (4.5rem), each multiplied by `--density` (set by `data-density`).
|
|
85
|
+
|
|
86
|
+
Radius — `--radius-small` `--radius-medium` `--radius-large` `--radius-full`, plus `--radius-button` and `--radius-field` (pill by default; `data-radius` presets remap them).
|
|
87
|
+
|
|
88
|
+
Type — `--text-1` (largest, fluid `clamp()`) through `--text-8` (0.75rem); `--text-regular` = `--text-6` (1rem). `--font-sans`, `--font-mono`; weights `--font-normal/medium/semibold/bold`.
|
|
89
|
+
|
|
90
|
+
Shadows — `--shadow-ring` (1px hairline), `--shadow-small/medium/large` (layered ring + lift + ambient; adapt to dark mode automatically).
|
|
91
|
+
|
|
92
|
+
Motion — `--transition-fast` (120ms), `--transition` (200ms), `--ease-out`. Layers/z — `--z-dropdown: 50`, `--z-modal: 200`. Misc — `--bar-height` (progress/meter).
|
|
93
|
+
|
|
94
|
+
## Typography
|
|
95
|
+
|
|
96
|
+
Semantic HTML is styled with no classes: headings, `<p>`, `<a>`, `<strong>`, `<em>`, `<code>`, `<mark>`, `<small>`, `<kbd>`, `<del>`/`<ins>`, `<s>`, `<u>`, `<abbr>`, `<sub>`/`<sup>`, `<samp>`, `<blockquote>`, lists, `<hgroup>` (heading + subtitle), `<figure>`/`<figcaption>`.
|
|
97
|
+
|
|
98
|
+
```html
|
|
99
|
+
<hgroup>
|
|
100
|
+
<h3>Heading</h3>
|
|
101
|
+
<p>Subtitle text.</p>
|
|
102
|
+
</hgroup>
|
|
103
|
+
|
|
104
|
+
<dl data-grid> <!-- definition list as a two-column grid -->
|
|
105
|
+
<dt>Version</dt><dd>1.x</dd>
|
|
106
|
+
<dt>License</dt><dd>MIT</dd>
|
|
107
|
+
</dl>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Buttons
|
|
111
|
+
|
|
112
|
+
A plain `<button>` is the primary action. Links become buttons with `class="button"`.
|
|
113
|
+
|
|
114
|
+
```html
|
|
115
|
+
<button>Primary</button>
|
|
116
|
+
<button data-variant="secondary">Secondary</button>
|
|
117
|
+
<button data-variant="danger">Danger</button>
|
|
118
|
+
<button class="outline">Outline</button>
|
|
119
|
+
<button class="ghost">Ghost</button>
|
|
120
|
+
<button disabled>Disabled</button>
|
|
121
|
+
<a href="/docs" class="button">Link button</a>
|
|
122
|
+
|
|
123
|
+
<button class="small">Small</button>
|
|
124
|
+
<button class="large">Large</button>
|
|
125
|
+
<button class="icon" aria-label="Add"><svg>…</svg></button> <!-- square icon button -->
|
|
126
|
+
<button><svg>…</svg> Download</button> <!-- icons inline just work -->
|
|
127
|
+
<button><span class="truncate">Very long label…</span></button>
|
|
128
|
+
|
|
129
|
+
<menu class="buttons"> <!-- fused button group -->
|
|
130
|
+
<li><button data-variant="secondary">Left</button></li>
|
|
131
|
+
<li><button data-variant="secondary">Middle</button></li>
|
|
132
|
+
<li><button data-variant="secondary">Right</button></li>
|
|
133
|
+
</menu>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- `data-variant`: `secondary`, `danger` (default = primary).
|
|
137
|
+
- Classes: `.outline`, `.ghost` (combinable with variants), sizes `.small`/`.large`/`.icon`.
|
|
138
|
+
- `aria-busy="true"` on a button shows an inline spinner (see Spinner).
|
|
139
|
+
|
|
140
|
+
## Badges
|
|
141
|
+
|
|
142
|
+
```html
|
|
143
|
+
<span class="badge">Default</span>
|
|
144
|
+
<span class="badge" data-variant="secondary">Secondary</span>
|
|
145
|
+
<span class="badge" data-variant="success">Success</span>
|
|
146
|
+
<span class="badge" data-variant="warning">Warning</span>
|
|
147
|
+
<span class="badge" data-variant="danger">Danger</span>
|
|
148
|
+
<span class="badge outline">Outline</span>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Avatars
|
|
152
|
+
|
|
153
|
+
```html
|
|
154
|
+
<figure data-variant="avatar" class="small">AB</figure>
|
|
155
|
+
<figure data-variant="avatar">CD</figure>
|
|
156
|
+
<figure data-variant="avatar" class="large">EF</figure>
|
|
157
|
+
|
|
158
|
+
<!-- stacked team: nest avatars -->
|
|
159
|
+
<figure data-variant="avatar" role="group" aria-label="Team">
|
|
160
|
+
<figure data-variant="avatar">JK</figure>
|
|
161
|
+
<figure data-variant="avatar">LM</figure>
|
|
162
|
+
</figure>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Navigation
|
|
166
|
+
|
|
167
|
+
```html
|
|
168
|
+
<nav data-breadcrumbs aria-label="Breadcrumb">
|
|
169
|
+
<ol>
|
|
170
|
+
<li><a href="/">Home</a></li>
|
|
171
|
+
<li><a href="/library">Library</a></li>
|
|
172
|
+
<li aria-current="page">Current page</li>
|
|
173
|
+
</ol>
|
|
174
|
+
</nav>
|
|
175
|
+
|
|
176
|
+
<nav data-menu aria-label="Section menu">
|
|
177
|
+
<ul>
|
|
178
|
+
<li><a href="/overview" aria-current="page">Overview</a></li>
|
|
179
|
+
<li><a href="/reports">Reports</a></li>
|
|
180
|
+
</ul>
|
|
181
|
+
</nav>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Sidebar layout (app shell)
|
|
185
|
+
|
|
186
|
+
A grid shell: full-width top bar, collapsible sidebar, scrollable main. The JS wires `[data-sidebar-toggle]` to flip `data-sidebar-open` on the layout; on mobile (≤768px) the sidebar is an off-canvas drawer that auto-dismisses on outside click or link click.
|
|
187
|
+
|
|
188
|
+
```html
|
|
189
|
+
<div data-sidebar-layout="always">
|
|
190
|
+
<nav data-topnav aria-label="Main">
|
|
191
|
+
<button data-sidebar-toggle aria-label="Toggle sidebar"><svg>…</svg></button>
|
|
192
|
+
<a href="/" class="brand">App</a>
|
|
193
|
+
</nav>
|
|
194
|
+
<aside data-sidebar>
|
|
195
|
+
<nav aria-label="Sections">
|
|
196
|
+
<p data-nav-label>Group label</p>
|
|
197
|
+
<ul>
|
|
198
|
+
<li><a href="/one" aria-current="page">One</a></li>
|
|
199
|
+
<li><a href="/two">Two</a></li>
|
|
200
|
+
</ul>
|
|
201
|
+
</nav>
|
|
202
|
+
</aside>
|
|
203
|
+
<main>…scrollable content…</main>
|
|
204
|
+
</div>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
- `data-sidebar-layout="always"` shows the collapse toggle on desktop too; bare `data-sidebar-layout` keeps the sidebar fixed on desktop.
|
|
208
|
+
- Inside `aside[data-sidebar]`: optional `> header` / `> footer`, and `> nav` with `[data-nav-label]` (or `h2`–`h6`) as group labels.
|
|
209
|
+
- `nav[data-topnav]` is sticky and also works standalone, outside the sidebar layout.
|
|
210
|
+
|
|
211
|
+
## Cards & grid
|
|
212
|
+
|
|
213
|
+
```html
|
|
214
|
+
<div class="row"> <!-- 12-column grid, stacks to 4 cols on mobile -->
|
|
215
|
+
<article class="card col-6"><h3>Half width</h3></article>
|
|
216
|
+
<article class="card col-6"><h3>Half width</h3></article>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<div class="autogrid" style="--autogrid-min: 8rem"> <!-- fits as many as space allows -->
|
|
220
|
+
<div class="card">auto</div>
|
|
221
|
+
<div class="card">auto</div>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div class="container">centered, max-width content</div>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
- Columns: `.col-1` … `.col-12`, offsets `.offset-1` … `.offset-6`, `.col-end` (push to end). Only meaningful as direct children of `.row`.
|
|
228
|
+
- Grid knobs: `--grid-gap`, `--autogrid-min`, `--container-max`, `--container-pad`.
|
|
229
|
+
|
|
230
|
+
## Forms
|
|
231
|
+
|
|
232
|
+
Native inputs, selects, checkboxes, radios, switches, ranges, file and search fields are styled directly — validation states included.
|
|
233
|
+
|
|
234
|
+
```html
|
|
235
|
+
<div data-field>
|
|
236
|
+
<label for="name">Name</label>
|
|
237
|
+
<input id="name" type="text" placeholder="Ada Lovelace" />
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<div data-field="error">
|
|
241
|
+
<label for="pw">Password</label>
|
|
242
|
+
<input id="pw" type="password" aria-invalid="true" aria-describedby="pw-err" />
|
|
243
|
+
<small class="error" id="pw-err">Too short — minimum 8 characters.</small>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
<div data-field>
|
|
247
|
+
<label for="role">Role</label>
|
|
248
|
+
<select id="role"><option>Engineer</option></select>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
<fieldset class="group"> <!-- fused prefix + input + button -->
|
|
252
|
+
<legend>https://</legend>
|
|
253
|
+
<input type="text" placeholder="your-site.com" aria-label="Website" />
|
|
254
|
+
<button type="button">Go</button>
|
|
255
|
+
</fieldset>
|
|
256
|
+
|
|
257
|
+
<label><input type="checkbox" checked /> Checkbox</label>
|
|
258
|
+
<label><input type="radio" name="plan" checked /> Radio</label>
|
|
259
|
+
<label><input type="checkbox" role="switch" checked /> Toggle switch</label>
|
|
260
|
+
<input type="range" min="0" max="100" value="60" />
|
|
261
|
+
|
|
262
|
+
<search>
|
|
263
|
+
<label for="q">Search</label>
|
|
264
|
+
<input id="q" type="search" placeholder="Search…" />
|
|
265
|
+
</search>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
- `data-field` wraps label + control + hints; `data-field="error"` tints the field.
|
|
269
|
+
- With the JS bundle, `<input type="password">` automatically gets a show/hide toggle. Opt out per input with `data-static`. (Inputs inside `fieldset.group` are skipped.)
|
|
270
|
+
|
|
271
|
+
## Tables
|
|
272
|
+
|
|
273
|
+
```html
|
|
274
|
+
<div class="table" tabindex="0" role="region" aria-label="Results table">
|
|
275
|
+
<table>
|
|
276
|
+
<caption>Bundle breakdown</caption>
|
|
277
|
+
<thead><tr><th>Component</th><th>Size</th></tr></thead>
|
|
278
|
+
<tbody><tr><td>Tabs</td><td>0.9 kB</td></tr></tbody>
|
|
279
|
+
<tfoot><tr><td>Total</td><td>0.9 kB</td></tr></tfoot>
|
|
280
|
+
</table>
|
|
281
|
+
</div>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The `.table` wrapper provides horizontal scrolling on small screens. Numbers are tabular by default.
|
|
285
|
+
|
|
286
|
+
## Accordion
|
|
287
|
+
|
|
288
|
+
Native `<details>` — adjacent ones fuse into a group automatically:
|
|
289
|
+
|
|
290
|
+
```html
|
|
291
|
+
<details open>
|
|
292
|
+
<summary>What is teff?</summary>
|
|
293
|
+
<p>An ultra-lightweight semantic component library.</p>
|
|
294
|
+
</details>
|
|
295
|
+
<details>
|
|
296
|
+
<summary>Does it need a build step?</summary>
|
|
297
|
+
<p>No.</p>
|
|
298
|
+
</details>
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Tabs — `<teff-tabs>` (custom element, requires JS)
|
|
302
|
+
|
|
303
|
+
Wires ARIA (`aria-controls`, `aria-labelledby`, ids), roving tabindex, and Arrow-key navigation around your markup. Tabs and panels pair by order.
|
|
304
|
+
|
|
305
|
+
```html
|
|
306
|
+
<teff-tabs>
|
|
307
|
+
<div role="tablist" aria-label="Example tabs">
|
|
308
|
+
<button role="tab" aria-selected="true">Overview</button>
|
|
309
|
+
<button role="tab">Features</button>
|
|
310
|
+
</div>
|
|
311
|
+
<div role="tabpanel"><p>Overview panel.</p></div>
|
|
312
|
+
<div role="tabpanel"><p>Features panel.</p></div>
|
|
313
|
+
</teff-tabs>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
- Initial tab: the one with `aria-selected="true"`, else the first.
|
|
317
|
+
- Keyboard: ArrowLeft/ArrowRight cycle, click activates.
|
|
318
|
+
- JS API: `el.activeIndex` (get/set, number).
|
|
319
|
+
- Event: `teff-tab-change` — bubbling `CustomEvent` with `detail: { index: number, tab: HTMLElement }`.
|
|
320
|
+
|
|
321
|
+
```js
|
|
322
|
+
document.querySelector("teff-tabs").addEventListener("teff-tab-change", (e) => {
|
|
323
|
+
console.log(e.detail.index, e.detail.tab);
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Dropdown — `<teff-dropdown>` (custom element, requires JS)
|
|
328
|
+
|
|
329
|
+
Built on the native Popover API; adds anchoring (with viewport flip), menu keyboard navigation (ArrowUp/ArrowDown wrap, Home/End), focus management, and `aria-expanded`.
|
|
330
|
+
|
|
331
|
+
```html
|
|
332
|
+
<teff-dropdown>
|
|
333
|
+
<button popovertarget="menu-demo" aria-haspopup="menu" aria-expanded="false">Options</button>
|
|
334
|
+
<menu popover id="menu-demo">
|
|
335
|
+
<button role="menuitem">Edit</button>
|
|
336
|
+
<button role="menuitem">Duplicate</button>
|
|
337
|
+
<hr />
|
|
338
|
+
<button role="menuitem" data-variant="danger">Delete</button>
|
|
339
|
+
</menu>
|
|
340
|
+
</teff-dropdown>
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Dialog
|
|
344
|
+
|
|
345
|
+
Native `<dialog>`, opened declaratively with `command`/`commandfor` (the JS bundle polyfills these for Safari). Clicking the backdrop closes it — opt out with `data-static` on the dialog.
|
|
346
|
+
|
|
347
|
+
```html
|
|
348
|
+
<button command="show-modal" commandfor="confirm-dialog">Open dialog</button>
|
|
349
|
+
<dialog id="confirm-dialog">
|
|
350
|
+
<header>
|
|
351
|
+
<h3>Delete project?</h3>
|
|
352
|
+
<p>This action cannot be undone.</p>
|
|
353
|
+
</header>
|
|
354
|
+
<div><p>The project will be permanently removed.</p></div>
|
|
355
|
+
<footer>
|
|
356
|
+
<button class="ghost" command="close" commandfor="confirm-dialog">Cancel</button>
|
|
357
|
+
<button data-variant="danger" command="close" commandfor="confirm-dialog">Delete</button>
|
|
358
|
+
</footer>
|
|
359
|
+
</dialog>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Structure: optional `<header>` (title + subtitle), body content, optional `<footer>` (actions).
|
|
363
|
+
|
|
364
|
+
## Alerts
|
|
365
|
+
|
|
366
|
+
```html
|
|
367
|
+
<div role="alert">Neutral message.</div>
|
|
368
|
+
<div role="alert" data-variant="success">Saved successfully.</div>
|
|
369
|
+
<div role="alert" data-variant="warning">Subscription expires soon.</div>
|
|
370
|
+
<div role="alert" data-variant="danger">Something went wrong.</div>
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
`data-variant`: `success`, `warning`, `danger` (alias `error`).
|
|
374
|
+
|
|
375
|
+
## Progress & meter
|
|
376
|
+
|
|
377
|
+
```html
|
|
378
|
+
<progress value="0.6" max="1"></progress>
|
|
379
|
+
<progress></progress> <!-- bare = indeterminate animation -->
|
|
380
|
+
<meter value="0.85" min="0" max="1">85%</meter>
|
|
381
|
+
<meter value="0.2" min="0" max="1" low="0.4" high="0.7" optimum="1">20%</meter> <!-- self-colors -->
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Spinner & skeleton
|
|
385
|
+
|
|
386
|
+
```html
|
|
387
|
+
<span aria-busy="true"></span> <!-- standalone spinner -->
|
|
388
|
+
<span aria-busy="true" data-spinner="small"></span> <!-- sizes: small, large -->
|
|
389
|
+
<button aria-busy="true">Loading</button> <!-- spinner inside button -->
|
|
390
|
+
<div aria-busy="true" data-spinner="overlay">…</div><!-- dims + disables contents -->
|
|
391
|
+
|
|
392
|
+
<div role="status" class="skeleton line"></div> <!-- shimmer placeholder -->
|
|
393
|
+
<div role="status" class="skeleton line" style="width: 60%"></div>
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
`aria-busy="true"` on anything shows a spinner; `data-spinner` accepts space-separated `small`/`large`/`overlay`.
|
|
397
|
+
|
|
398
|
+
## Tooltips
|
|
399
|
+
|
|
400
|
+
With the JS bundle, every `title` attribute is automatically converted into a styled tooltip (`data-tooltip` + `aria-label`) — including elements added later.
|
|
401
|
+
|
|
402
|
+
```html
|
|
403
|
+
<button class="outline" title="Tooltip text" data-tooltip-placement="top">Hover me</button>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
- `data-tooltip-placement`: `top` (default), `bottom`, `left`, `right`.
|
|
407
|
+
- CSS-only usage (no JS): set `data-tooltip="text"` and `aria-label` yourself.
|
|
408
|
+
|
|
409
|
+
## Toasts — `teff.toast()` (JS API)
|
|
410
|
+
|
|
411
|
+
```js
|
|
412
|
+
teff.toast("Saved!");
|
|
413
|
+
teff.toast("Your changes have been saved.", "Saved", { variant: "success" });
|
|
414
|
+
teff.toast("Something went wrong.", "Error", { variant: "danger", placement: "bottom-center" });
|
|
415
|
+
teff.toast("Sticky until dismissed.", "Note", { duration: 0 });
|
|
416
|
+
|
|
417
|
+
// custom markup (an element or a <template>) — cloned, id stripped
|
|
418
|
+
teff.toast.el(document.querySelector("#my-template"), { duration: 4000 });
|
|
419
|
+
|
|
420
|
+
teff.toast.clear(); // clear all
|
|
421
|
+
teff.toast.clear("top-right"); // clear one placement
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Signature: `teff.toast(message: string, title?: string, options?)` → the toast element.
|
|
425
|
+
|
|
426
|
+
| Option | Values | Default |
|
|
427
|
+
|---|---|---|
|
|
428
|
+
| `variant` | `info`, `success`, `warning`, `danger` | `info` |
|
|
429
|
+
| `placement` | `top-left`, `top-center`, `top-right`, `bottom-left`, `bottom-center`, `bottom-right` | `top-right` |
|
|
430
|
+
| `duration` | ms; `0` = never auto-dismiss | `4000` |
|
|
431
|
+
|
|
432
|
+
Hovering a toast pauses its timer. `teff.toast.el(elementOrTemplate, { placement, duration })` shows arbitrary markup.
|
|
433
|
+
|
|
434
|
+
## Motion
|
|
435
|
+
|
|
436
|
+
Opt-in, compositor-friendly (only `opacity`, `translate`, `filter`); everything respects `prefers-reduced-motion`.
|
|
437
|
+
|
|
438
|
+
```html
|
|
439
|
+
<div data-enter>fades in once on load</div>
|
|
440
|
+
<div data-enter="stagger"> <!-- children cascade 100 ms apart -->
|
|
441
|
+
<h3>Title</h3>
|
|
442
|
+
<p>Body</p>
|
|
443
|
+
</div>
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
```js
|
|
447
|
+
teff.shake(inputEl); // quick head-shake for rejected input; restarts cleanly on repeat calls
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
`teff.shake(el)` toggles the `data-shake` attribute (which you can also set manually) and removes it on `animationend`.
|
|
451
|
+
|
|
452
|
+
## Utilities
|
|
453
|
+
|
|
454
|
+
Layout: `.flex` `.flex-col` `.items-center` `.justify-center` `.justify-between` `.justify-end` `.grow` `.shrink-0` `.min-w-0` `.w-100` · stacks: `.hstack` (horizontal, gap, wraps) `.vstack` (vertical, gap) · gaps: `.gap-1/2/3/4/6`.
|
|
455
|
+
|
|
456
|
+
Spacing: `.mt-2/4/6/8` `.mb-2/4/6/8` `.ms-auto` `.me-auto` `.mx-auto` `.p-2/4/6`.
|
|
457
|
+
|
|
458
|
+
Text: `.align-left/center/right` `.text-light` `.text-lighter` `.text-small` `.text-xsmall` `.font-normal/medium/semibold` `.tabular` (tabular numerals) `.truncate` `.sr-only`.
|
|
459
|
+
|
|
460
|
+
Surfaces: `.rounded` `.rounded-large` `.rounded-full` `.shadow-small/medium/large` `.img-outline` (1px alpha edge on images) · `.unstyled` (on `ul`/`ol`/`a`: strips list/link styling).
|
|
461
|
+
|
|
462
|
+
## window.teff global (JS API summary)
|
|
463
|
+
|
|
464
|
+
| API | Description |
|
|
465
|
+
|---|---|
|
|
466
|
+
| `teff.toast(message, title?, opts?)` | show a text toast; returns the element |
|
|
467
|
+
| `teff.toast.el(el, opts?)` | show an element or `<template>` as a toast |
|
|
468
|
+
| `teff.toast.clear(placement?)` | dismiss all toasts, or one placement |
|
|
469
|
+
| `teff.shake(el)` | shake animation on an element |
|
|
470
|
+
|
|
471
|
+
Custom elements registered: `teff-tabs`, `teff-dropdown`. Automatic page-wide enhancements: tooltips from `title`, password show/hide toggles, sidebar toggle wiring, `command`/`commandfor` polyfill, dialog backdrop-close.
|
|
472
|
+
|
|
473
|
+
TypeScript: the package ships `teff.d.ts` (the `teff` global, `HTMLElementTagNameMap` entries, and the `teff-tab-change` event map) and a `custom-elements.json` manifest.
|
|
474
|
+
|
|
475
|
+
## React / JSX notes
|
|
476
|
+
|
|
477
|
+
teff is plain markup — classes and `data-*` attributes work in JSX as-is. Import both files once in your entry module.
|
|
478
|
+
|
|
479
|
+
- React 19 renders custom elements like `<teff-tabs>` natively; for TypeScript, declare them in `JSX.IntrinsicElements` (or use the shipped `teff.d.ts` tag name map).
|
|
480
|
+
- teff components emit plain DOM `CustomEvent`s — listen with a `ref` + `addEventListener`; React does not map custom events to `on*` props.
|
|
481
|
+
|
|
482
|
+
## Accessibility & browser support
|
|
483
|
+
|
|
484
|
+
WCAG AA contrast in both schemes; keyboard navigation per WAI-ARIA patterns (tabs, menus); `prefers-reduced-motion` respected throughout. Requires a modern evergreen browser: `light-dark()`, Popover API, `@layer`, `:has()`.
|
|
485
|
+
|
|
486
|
+
## License
|
|
487
|
+
|
|
488
|
+
MIT. A fork of [oat](https://github.com/knadh/oat) by knadh.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0.0",
|
|
3
|
+
"readme": "README.md",
|
|
4
|
+
"modules": [
|
|
5
|
+
{
|
|
6
|
+
"kind": "javascript-module",
|
|
7
|
+
"path": "js/tabs.js",
|
|
8
|
+
"summary": "Tabs custom element: ARIA wiring and arrow-key navigation around existing tablist markup.",
|
|
9
|
+
"declarations": [
|
|
10
|
+
{
|
|
11
|
+
"kind": "class",
|
|
12
|
+
"name": "TeffTabs",
|
|
13
|
+
"tagName": "teff-tabs",
|
|
14
|
+
"customElement": true,
|
|
15
|
+
"summary": "Accessible tabs from semantic child markup.",
|
|
16
|
+
"description": "Expects a child [role=\"tablist\"] containing [role=\"tab\"] buttons, followed by sibling [role=\"tabpanel\"] elements paired by order. Generates ids, aria-controls and aria-labelledby, manages aria-selected, hidden panels, roving tabindex, and ArrowLeft/ArrowRight navigation. The initially active tab is the one with aria-selected=\"true\", else the first. No Shadow DOM; the light-DOM markup is enhanced in place.",
|
|
17
|
+
"members": [
|
|
18
|
+
{
|
|
19
|
+
"kind": "field",
|
|
20
|
+
"name": "activeIndex",
|
|
21
|
+
"type": { "text": "number" },
|
|
22
|
+
"description": "Zero-based index of the active tab. Setting it activates that tab."
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"events": [
|
|
26
|
+
{
|
|
27
|
+
"name": "teff-tab-change",
|
|
28
|
+
"type": { "text": "CustomEvent<{ index: number, tab: HTMLElement }>" },
|
|
29
|
+
"description": "Fired (bubbling, composed) whenever a tab is activated, including the initial activation."
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"superclass": { "name": "TeffBase", "module": "js/base.js" }
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"exports": [
|
|
36
|
+
{
|
|
37
|
+
"kind": "custom-element-definition",
|
|
38
|
+
"name": "teff-tabs",
|
|
39
|
+
"declaration": { "name": "TeffTabs", "module": "js/tabs.js" }
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"kind": "javascript-module",
|
|
45
|
+
"path": "js/dropdown.js",
|
|
46
|
+
"summary": "Dropdown custom element: positioning and keyboard navigation for native popover menus.",
|
|
47
|
+
"declarations": [
|
|
48
|
+
{
|
|
49
|
+
"kind": "class",
|
|
50
|
+
"name": "TeffDropdown",
|
|
51
|
+
"tagName": "teff-dropdown",
|
|
52
|
+
"customElement": true,
|
|
53
|
+
"summary": "Anchored popover menu with keyboard navigation.",
|
|
54
|
+
"description": "Expects a child [popovertarget] trigger button and a [popover] menu (typically <menu popover id=\"...\">) containing [role=\"menuitem\"] buttons. Anchors the open menu to the trigger, flipping when it would overflow the viewport; focuses the first item on open; ArrowUp/ArrowDown wrap, Home/End jump; manages aria-expanded on the trigger and returns focus on close. No Shadow DOM.",
|
|
55
|
+
"members": [],
|
|
56
|
+
"events": [],
|
|
57
|
+
"superclass": { "name": "TeffBase", "module": "js/base.js" }
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"exports": [
|
|
61
|
+
{
|
|
62
|
+
"kind": "custom-element-definition",
|
|
63
|
+
"name": "teff-dropdown",
|
|
64
|
+
"declaration": { "name": "TeffDropdown", "module": "js/dropdown.js" }
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
package/llms.txt
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# teff
|
|
2
|
+
|
|
3
|
+
> teff is an ultra-lightweight semantic UI library: one CSS file (~9 kB gzip) and one JS file (~3 kB gzip), independently usable. The CSS styles plain semantic HTML (buttons, forms, tables, dialog, details, progress…) with automatic dark mode via light-dark(). The JS adds two custom elements (teff-tabs, teff-dropdown), automatic enhancements (tooltips from title attributes, password show/hide, sidebar shell), and imperative APIs teff.toast() and teff.shake(). No framework, no build step, no dependencies. npm: @thinking.tools/teff. MIT.
|
|
4
|
+
|
|
5
|
+
Key facts: write semantic HTML and it is already styled; variants use data-variant attributes; theme presets are attributes on <html> (data-theme, data-accent, data-radius, data-density); all styling flows through CSS custom properties.
|
|
6
|
+
|
|
7
|
+
## Docs
|
|
8
|
+
|
|
9
|
+
- [Complete reference](https://unpkg.com/@thinking.tools/teff/REFERENCE.md): every component, token, and JS API with copy-paste HTML — single file, also served as llms-full.txt
|
|
10
|
+
- [README](https://unpkg.com/@thinking.tools/teff/README.md): install and quick start
|
|
11
|
+
|
|
12
|
+
## API metadata
|
|
13
|
+
|
|
14
|
+
- [custom-elements.json](https://unpkg.com/@thinking.tools/teff/custom-elements.json): machine-readable manifest of the custom elements
|
|
15
|
+
- [teff.d.ts](https://unpkg.com/@thinking.tools/teff/teff.d.ts): TypeScript declarations for the teff global and tag names
|
|
16
|
+
|
|
17
|
+
## Source & demos
|
|
18
|
+
|
|
19
|
+
- [Live component gallery](https://thinking_tools.codeberg.page/teff/): every component rendered, with source panels
|
|
20
|
+
- [Repository](https://codeberg.org/thinking_tools/teff): source code (src/css, src/js)
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
- CSS: https://unpkg.com/@thinking.tools/teff/teff.min.css
|
|
25
|
+
- JS: https://unpkg.com/@thinking.tools/teff/teff.min.js
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thinking.tools/teff",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
6
6
|
},
|
|
@@ -23,15 +23,28 @@
|
|
|
23
23
|
"teff.min.css",
|
|
24
24
|
"teff.min.js",
|
|
25
25
|
"css",
|
|
26
|
-
"js"
|
|
26
|
+
"js",
|
|
27
|
+
"teff.d.ts",
|
|
28
|
+
"custom-elements.json",
|
|
29
|
+
"llms.txt",
|
|
30
|
+
"REFERENCE.md"
|
|
27
31
|
],
|
|
28
32
|
"author": "https://codeberg.org/thinking_tools",
|
|
29
33
|
"license": "MIT",
|
|
30
34
|
"description": "An ultra-lightweight semantic UI library: one CSS file, one JS file. No framework, no build step, no dependencies — dark mode built in.",
|
|
31
35
|
"main": "teff.min.js",
|
|
36
|
+
"types": "./teff.d.ts",
|
|
32
37
|
"style": "teff.min.css",
|
|
33
38
|
"unpkg": "teff.min.js",
|
|
34
39
|
"jsdelivr": "teff.min.js",
|
|
40
|
+
"customElements": "custom-elements.json",
|
|
41
|
+
"exports": {
|
|
42
|
+
".": {
|
|
43
|
+
"types": "./teff.d.ts",
|
|
44
|
+
"default": "./teff.min.js"
|
|
45
|
+
},
|
|
46
|
+
"./*": "./*"
|
|
47
|
+
},
|
|
35
48
|
"devDependencies": {
|
|
36
49
|
"esbuild": "^0.28.0",
|
|
37
50
|
"eslint": "^10.4.1"
|
package/teff.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for @thinking.tools/teff.
|
|
3
|
+
* The JS bundle is an IIFE: importing the package registers the custom
|
|
4
|
+
* elements and installs the `teff` global as side effects.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type ToastVariant = "info" | "success" | "warning" | "danger";
|
|
8
|
+
|
|
9
|
+
export type ToastPlacement =
|
|
10
|
+
| "top-left"
|
|
11
|
+
| "top-center"
|
|
12
|
+
| "top-right"
|
|
13
|
+
| "bottom-left"
|
|
14
|
+
| "bottom-center"
|
|
15
|
+
| "bottom-right";
|
|
16
|
+
|
|
17
|
+
export interface ToastShowOptions {
|
|
18
|
+
/** Where the toast stack appears. Default: "top-right". */
|
|
19
|
+
placement?: ToastPlacement;
|
|
20
|
+
/** Auto-dismiss after this many ms; 0 disables auto-dismiss. Default: 4000. */
|
|
21
|
+
duration?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ToastOptions extends ToastShowOptions {
|
|
25
|
+
/** Visual tone of the toast. Default: "info". */
|
|
26
|
+
variant?: ToastVariant;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ToastFn {
|
|
30
|
+
/** Show a text toast. Returns the created toast element. */
|
|
31
|
+
(message: string, title?: string, options?: ToastOptions): HTMLOutputElement;
|
|
32
|
+
/**
|
|
33
|
+
* Show an element as a toast. A `<template>` contributes its first child;
|
|
34
|
+
* any other element is cloned. Returns the shown element, or undefined if
|
|
35
|
+
* there was nothing to show.
|
|
36
|
+
*/
|
|
37
|
+
el(el: Element, options?: ToastShowOptions): HTMLElement | undefined;
|
|
38
|
+
/** Dismiss all toasts, or only those in the given placement. */
|
|
39
|
+
clear(placement?: ToastPlacement): void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface Teff {
|
|
43
|
+
toast: ToastFn;
|
|
44
|
+
/**
|
|
45
|
+
* Play the [data-shake] attention animation on an element. Safe to call
|
|
46
|
+
* repeatedly; restarts a mid-flight animation. No-op when el is null.
|
|
47
|
+
*/
|
|
48
|
+
shake(el: Element | null): void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TeffTabChangeDetail {
|
|
52
|
+
/** Zero-based index of the newly active tab. */
|
|
53
|
+
index: number;
|
|
54
|
+
/** The activated [role="tab"] element. */
|
|
55
|
+
tab: HTMLElement;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* `<teff-tabs>` — wires ARIA and arrow-key navigation around child
|
|
60
|
+
* [role="tablist"]/[role="tab"]/[role="tabpanel"] markup.
|
|
61
|
+
* Emits "teff-tab-change" (bubbling) on activation.
|
|
62
|
+
*/
|
|
63
|
+
export declare class TeffTabs extends HTMLElement {
|
|
64
|
+
/** Zero-based index of the active tab. Setting it activates that tab. */
|
|
65
|
+
activeIndex: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* `<teff-dropdown>` — anchors a child [popover] menu to its
|
|
70
|
+
* [popovertarget] trigger and adds menu keyboard navigation.
|
|
71
|
+
*/
|
|
72
|
+
export declare class TeffDropdown extends HTMLElement {}
|
|
73
|
+
|
|
74
|
+
declare global {
|
|
75
|
+
/** Installed by the teff JS bundle. */
|
|
76
|
+
var teff: Teff;
|
|
77
|
+
|
|
78
|
+
interface Window {
|
|
79
|
+
teff: Teff;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface HTMLElementTagNameMap {
|
|
83
|
+
"teff-tabs": TeffTabs;
|
|
84
|
+
"teff-dropdown": TeffDropdown;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface GlobalEventHandlersEventMap {
|
|
88
|
+
"teff-tab-change": CustomEvent<TeffTabChangeDetail>;
|
|
89
|
+
}
|
|
90
|
+
}
|