Basic Usage
Load the global design contract once, then register only the components used by the page.
import '@vielzeug/refine/tokens.css';
import '@vielzeug/refine/button';
import '@vielzeug/refine/input';
import '@vielzeug/refine/dialog';
const input = document.querySelector('ore-input');
const save = document.querySelector('ore-button');
if (!(input instanceof HTMLElement) || !(save instanceof HTMLElement)) {
throw new Error('Expected Refine controls');
}
save.addEventListener('click', () => {
console.log((input as HTMLElement & { value: string }).value);
});Add the browser reset only when the application owns global element styling:
import '@vielzeug/refine/styles/preflight.css';Choose an Icon Library
Refine includes only its internal component icons. Register application icons before mounting them: use registerIcons and neutral IconDefinition data from @vielzeug/refine/icons, the pure @vielzeug/refine/icons/lucide adapter for selected Lucide icons, or @vielzeug/refine/icons/iconify for local Heroicons, Tabler, and Material Design Icons data. Keep importing the component from @vielzeug/refine/icon. Lucide is an optional peer: install lucide yourself only when choosing its data or @vielzeug/refine/icons/lucide/all; core and structural adapters do not require it. The icon reference covers imports, licenses, and presentation behavior.
For selected individual Iconify data, use heroiconsIcons, tablerIcons, or mdiIcons from @vielzeug/refine/icons/heroicons, /tabler, or /mdi. For example, registerIcons(heroiconsIcons({ Search, Home })) preserves your app keys. Install the matching @iconify-icons/<family> data package yourself; these helpers do not accept framework components. Keep iconifyIcons for collection JSON and aliases.
Set Attributes and Properties
Use attributes for serializable HTML state.
<ore-button variant="outline" color="secondary" size="lg" disabled>
Save changes
</ore-button>Use JavaScript properties for arrays, objects, callbacks, and controlled state.
import '@vielzeug/refine/datagrid';
import type { OreDataGridProps } from '@vielzeug/refine/datagrid';
const grid = document.querySelector('ore-datagrid') as HTMLElement & OreDataGridProps;
grid.columns = [
{ key: 'name', label: 'Name', sortable: true },
{ key: 'role', label: 'Role' },
];
grid.rows = [
{ id: '1', name: 'Ada', role: 'Admin' },
{ id: '2', name: 'Linus', role: 'Editor' },
];Enumerated properties such as variant, size, and color accept the values listed on each component's reference page. An unsupported value resolves to the property's declared default (or undefined when it has no default).
In an Ore template, model() can bind a Refine field's value to a Ripple signal and write user input back:
import { signal } from '@vielzeug/ripple';
import { html, model } from '@vielzeug/ore';
import '@vielzeug/refine/input';
const email = signal('');
const field = html`<ore-input label="Email" type="email" value=${model(email)} />`;Handle Events
Form controls dispatch standard input and change events. Read their current property from event.currentTarget.
const input = document.querySelector('ore-input');
input?.addEventListener('input', (event) => {
const target = event.currentTarget as HTMLElement & { value: string };
console.log(target.value);
});Custom events use kebab-case names and typed detail objects.
import type { OreDialogEvents } from '@vielzeug/refine/dialog';
const dialog = document.querySelector('ore-dialog');
dialog?.addEventListener('open-change', (event: CustomEvent<OreDialogEvents['open-change']>) => {
console.log(event.detail.open, event.detail.reason);
});Native Form Validation
Refine's form-associated controls integrate with native form submission, FormData, validity checks, and reset. Use required for a required-value constraint; setting error displays and announces feedback but does not, by itself, invalidate the form.
<form id="profile-form">
<ore-input name="email" type="email" label="Email" required></ore-input>
<ore-select name="role" label="Role" required>
<option value="">Choose a role…</option>
<option value="editor">Editor</option>
<option value="admin">Admin</option>
</ore-select>
<ore-button type="submit">Save</ore-button>
</form>
<script type="module">
import '@vielzeug/refine/input';
import '@vielzeug/refine/select';
import '@vielzeug/refine/button';
const form = document.querySelector('#profile-form');
form.addEventListener('submit', (event) => {
event.preventDefault();
console.log(new FormData(form).get('email'));
});
</script>The browser runs native validation before dispatching submit. Call form.reportValidity() to show validation feedback directly, or form.checkValidity() for a boolean check without the browser's validation UI.
Compose Slots
Default slots hold primary content. Named slots identify component-owned regions.
<ore-card>
<span slot="header">Account</span>
<p>Profile settings</p>
<div slot="footer">
<ore-button variant="ghost">Cancel</ore-button>
<ore-button>Save</ore-button>
</div>
</ore-card>Input components commonly expose prefix, suffix, label, helper, and error slots. Each component reference lists its supported slots.
Load TypeScript Framework Declarations
Import the declaration matching the framework once in an application type entry point.
import type {} from '@vielzeug/refine/frameworks/elements';import type {} from '@vielzeug/refine/frameworks/react';import type {} from '@vielzeug/refine/frameworks/vue';These entry points provide typed tags and component properties. They do not register components or install runtime adapters.
Framework Integration
Refine elements use native DOM properties, events, and slots. Frameworks differ only in how they forward custom-element properties and events.
import '@vielzeug/refine/button';
export function SaveButton() {
return <ore-button variant="solid">Save</ore-button>;
}<script setup lang="ts">
import '@vielzeug/refine/button';
</script>
<template>
<ore-button variant="solid">Save</ore-button>
</template><script lang="ts">
import '@vielzeug/refine/button';
</script>
<ore-button variant="solid">Save</ore-button>See Framework Integration for custom events, controlled properties, SSR guards, and framework-specific configuration.
Working with Other Vielzeug Libraries
Ore
Use Ore when authoring a custom element that composes Refine components.
import '@vielzeug/refine/button';
import { define, html } from '@vielzeug/ore';
define('save-panel', {
setup() {
return html`<ore-button @click=${() => console.log('save')}>Save</ore-button>`;
},
});Ripple
Use Ripple to drive component properties from application state.
import '@vielzeug/refine/button';
import { effect, signal } from '@vielzeug/ripple';
const loading = signal(false);
const button = document.querySelector('ore-button') as HTMLElement & { loading?: boolean };
const loadingEffect = effect(() => {
button.loading = loading.value;
});
loadingEffect.dispose();Best Practices
- Import
tokens.cssonce before rendering components. - Register components through explicit subpaths.
- Set structured data and callbacks as JavaScript properties.
- Read form state from the event target instead of custom event payloads.
- Provide accessible labels for icon-only controls.
- Use component reference pages for supported slots, parts, and CSS properties.
- Dispose application-owned Ripple effects and service handles.
- Guard DOM-dependent component registration during SSR.
- Working with AI agents? Install the
vielzeugskill (npx -y @vielzeug/codex skills install) so agents reach for Refine components and tokens instead of custom widgets: see the Codex usage guide.