Thyme UI
Native Web Component UI library · zero dependencies
th-field
Input types
Date picker
selected: 2026-05-13
<th-field label="Date picker" type="date"></th-field>
<script>
field.addEventListener('change', () => {
console.log(field.value);
});
</script>
Textarea
If there's content inside
If there's content inside
Validation
Ignored content
Ignored content
Custom error
field.setCustomValidity('Custom error message');
field.setCustomValidity(''); // clear
Disabled & Readonly
Value binding
Value: (empty)
field.addEventListener('input', () => {
display.textContent = field.value;
});
Custom content (slot)
th-switch
Basic
Disabled
Toggle event
switchEl.addEventListener('change', function (e) {
status.textContent = e.detail.checked ? 'On' : 'Off';
});
th-check
Checkbox
Radio group
Disabled
Change event
checkEl.addEventListener('change', function (e) {
status.textContent = e.detail.checked ? 'checked' : 'unchecked';
});
th-select
Basic
With label & value
Disabled
Change event
selected: (none)
selectEl.addEventListener('change', function (e) {
status.textContent = 'selected: ' + e.detail.value;
});
th-dialog
Basic alert
<th-dialog id="dlg" title="提示">
<p>操作已成功完成。</p>
<button slot="footer">好</button>
</th-dialog>
Confirm (two buttons)
<th-dialog id="dlg" title="确认删除">
<p>确定要删除吗?</p>
<button slot="footer" id="cancel">取消</button>
<button slot="footer" id="ok">删除</button>
</th-dialog>
No title
Non-closable (ESC disabled)
Custom width
<th-dialog title="Wide" width="500">...</th-dialog>
操作已成功完成。
此操作不可撤销,确定要删除吗?
这是一个没有标题栏的对话框。
按 ESC 无法关闭,必须点击按钮。
This dialog has width="500".
Shared color
All components read --th-primary from their parent. Set it once to theme everything.
对话框按钮颜色同样跟随 --th-primary。
<div style="--th-primary:#7c3aed">
<th-button variant="tonal">Purple</th-button>
<th-button variant="outlined">Purple</th-button>
<th-switch checked></th-switch>
<th-check checked>Purple</th-check>
<th-field label="Purple input" value="Shared theme"></th-field>
<th-select label="Choose" value="2">...</th-select>
</div>
th-toast
Standalone toast component with slide-in animation, auto-dismiss, click-to-dismiss.
Attributes
<th-toast type="info|warn|error|success" duration="3">message</th-toast>
Thyme.alert / Thyme.confirm
Programmatic dialog via Thyme.alert(message, title?) and Thyme.confirm(message, title?) — returns a Promise.
// One button — auto-resolves on click
Thyme.alert('Operation completed.', 'Success');
// Two buttons — returns Promise<boolean>
Thyme.confirm('Are you sure?', 'Confirm').then(result => {
// true = OK clicked, false = Cancel clicked
});
Thyme.info / .warn / .error / .success
Programmatic toasts. Second parameter is custom duration in seconds (default 3).
Thyme.info('Welcome!');
Thyme.warn('Be careful');
Thyme.error('Failed');
Thyme.success('Done!');
Thyme.warn('Custom duration', 5); // 5 seconds
Thyme.locale
Toggle between 'en' and 'zh'. Affects date picker, alert/confirm buttons, and internally via locale.translate(key).
// Get
console.log(Thyme.locale); // 'en' | 'zh'
// Set
Thyme.locale = 'zh';
Thyme.form
Serialize form data to/from JSON objects. Handles all [name] elements including radio, checkbox, switch, select.
// Collect all [name] values from a single scope
const data = Thyme.form.getJsonObject(formEl);
// Returns null if ANY field fails checkValidity()
// Collect from multiple scopes (e.g. table rows)
const rows = Thyme.form.getJsonArray('.form-row');
// Returns null if ANY scope fails
// Set values back into a scope
Thyme.form.setJsonObject(formEl, { name: "Alice", interest: ["rust"] });
// Validate first, then submit
const data = Thyme.form.getJsonObject('#my-form');
if (data === null) return; // ⬅ validation failed, already focused on first invalid field
await fetch('/api/submit', { method: 'POST', body: JSON.stringify(data) });
Thyme.http
Lightweight HTTP client wrapping fetch() with JSON handling, response auto-detection, and error reporting.
Methods
Thyme.http.get(url, opts?)
Thyme.http.post(url, data?, opts?)
Thyme.http.put(url, data?, opts?)
Thyme.http.patch(url, data?, opts?)
Thyme.http.delete(url, opts?)
Basic usage
// GET
const users = await Thyme.http.get('/api/users');
// POST with JSON body
const user = await Thyme.http.post('/api/users', { name: 'Marco' });
// PUT / PATCH / DELETE
await Thyme.http.put('/api/users/1', { name: 'Updated' });
await Thyme.http.patch('/api/users/1', { age: 31 });
await Thyme.http.delete('/api/users/1');
Custom fetch options
Pass native fetch options like signal, credentials, or custom headers via the last argument.
// Abort request
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 3000);
const data = await Thyme.http.get('/api/slow', { signal: ctrl.signal });
// Custom headers (merged with defaults)
Thyme.http.post('/api/data', { foo: 1 }, {
headers: { Authorization: 'Bearer xxx' }
});
// FormData (not JSON-stringified)
const fd = new FormData();
fd.append('file', blob);
await Thyme.http.post('/api/upload', fd);
Response types
Body is parsed automatically based on Content-Type:
// text/* → response.text()
// application/json → response.json()
// everything else → response.blob()
// Non-OK status throws with the parsed server message
try {
await Thyme.http.get('/api/data');
} catch (e) {
console.error(e.message);
}
Utilities
Called via Thyme.utils.*.
delay(ms)
Promise-based setTimeout — await Thyme.utils.delay(1000) waits 1 second.
await Thyme.utils.delay(1000);
console.log('1 second later');
nanoId(size = 24)
URL-safe unique ID generator (A-Za-z0-9, no - _).
Thyme.utils.nanoId(); // e.g. "Xa3Rt9Kf2LmNpQwZbY7VcJ1"
Thyme.utils.nanoId(12); // custom length
formatDate(date, pattern, utc?)
Format a Date object with custom pattern. Supports yyyy/yy/MM/M/dd/d/hh/h/mm/m/ss/s/SSS/S.
yyyy-MM-dd
yyyy/MM/dd hh:mm:ss
Thyme.utils.formatDate(new Date(), 'yyyy-MM-dd'); // 2026-05-13
Thyme.utils.formatDate(new Date(), 'yyyy/MM/dd hh:mm'); // 2026/05/13 14:30
Thyme.utils.formatDate(new Date(), 'MM/dd', true); // UTC version
formatMoney(number)
Format as currency with 2 decimal places and thousand separators.
1234567.89
Thyme.utils.formatMoney(1234567.89); // "1,234,567.89"
formatBytes(bytes)
Human-readable byte sizes (B, KiB, MiB, GiB, TiB...).
0
1024
1048576
1073741824
Thyme.utils.formatBytes(1024); // "1 KiB"
Thyme.utils.formatBytes(1048576); // "1 MiB"
Thyme.utils.formatBytes(1073741824); // "1 GiB"
formatSeconds(seconds)
Format seconds to compact d h m s string.
3661
90061
Thyme.utils.formatSeconds(3661); // "1h 1m 1s"
Thyme.utils.formatSeconds(90061); // "1d 1h 1m 1s"
parseDuration(string)
Parse HH:mm:ss.SSS to milliseconds.
01:30:00
00:05:30.500
Thyme.utils.parseDuration('01:30:00'); // 5400000 (1.5 hours)
Thyme.utils.parseDuration('00:05:30.500'); // 330500