declario
v0.1.2
Published
Tiny declarative UI runtime with signals, router, scoped styles, i18n, fetch tuples, portals.
Readme
DECLARIO
Minimal declarative UI runtime for TypeScript — no VDOM, just signals → DOM.
This monorepo contains:
See Declario's official documentation.
Library (what you import)
Core exports:
- DOM:
h,decl,fragment,raw,t - Reactivity:
signal,computed,effect - Flow:
Show,Switch,Match,For - Router:
Link,RoutesCreator,navigate,isRouteActive - Styles:
withScopedStyles,ensureScopedStyles(:host,:global(...)) - Data:
fetchWithDeclario,createFetchTuple,fetchJSON,once - i18n:
initI18n,setLocale,tr - Lifecycle/Portals (Coming Soon):
onMount,onCleanup,Portal - Devtools (Coming Soon):
bootDevtools(optional subpath)
Hello world
import { decl, fragment } from 'declario';
export function App() {
return fragment(
decl('h1', null, 'Hello World'),
decl('p', null, 'This is pretty cool')
);
}Counter (signals)
import { h, t, signal } from 'declario';
export function Counter() {
const count = signal(0);
const inc = () => count.set(count.get() + 1);
const dec = () => count.set(count.get() - 1);
return h('div', { class: 'counter' },
h('button', { 'on:click': dec, 'aria-label': 'decrement' }, '-'),
h('span', { className: 'counter-value' }, t(count.get)),
h('button', { onClick: inc, 'aria-label': 'increment' }, '+')
);
}Find more info about all the possibilities you have to write events (on:click, onclick or onClick) or props like classes (class and className).
Router
import { h, fragment, Link, RoutesCreator } from 'declario';
const routes = [
{ path: '/', view: () => h('h2', null, 'Home') },
{ path: '/about', view: () => h('h2', null, 'About') },
{ path: '/users/:id', view: (p) => h('h2', null, `User ${p.id}`) },
{ path: '*', view: () => h('div', null, '404 not found') },
];
export function App() {
return fragment(
h('nav', null,
Link({ href: '/', class: 'nav__link', activeClass: 'is-active', children: ['Home'] }),
' · ',
Link({ href: '/about', class: 'nav__link', activeClass: 'is-active', children: ['About'] })
),
RoutesCreator({ routes })
);
}Scoped styles
import { h, withScopedStyles } from 'declario';
const css = `
:host { display:inline-flex; margin:.25rem; }
.btn { padding:.5rem .75rem; border:1px solid #484848; border-radius:.5rem; background:#6d6767; color:#fff; }
.btn:hover { background:#5a4848; }
:global(body){ background:#f8f8f8; }
`;
export function Button(props: { onClick?: (e: MouseEvent) => void; children?: any[] } = {}) {
const el = h('button', { class: 'btn', onClick: props.onClick }, ...(props.children ?? []));
return withScopedStyles(css, el, 'Button');
}i18n
import { initI18n, setLocale, tr, t, h, decl } from 'declario';
import en from './locales/en';
import es from './locales/es';
initI18n({
defaultLocale: 'en',
dictionaries: [['en', en], ['es', es]],
persist: { mode: 'cookie', name: 'locale', sameSite: 'Lax', secure: false }
});
const switchToEN = () => setLocale('en');
const switchToES = () => setLocale('es');
export function LangDemo() {
return h('div', { class: 'lang-switch' },
h('button', { onClick: switchToEN }, 'EN'),
h('button', { onClick: switchToES }, 'ES'),
decl('p', null, tr('home.title')),
decl('p', null, tr('home.welcome', { name: 'Manuel' })),
);
}Fetch tuples (declarative data)
import { fetchWithDeclario, h, fragment, text, For } from 'declario';
const getUsers = fetchWithDeclario({
fetch: { url: 'https://jsonplaceholder.typicode.com/users', method: 'GET' },
options: { loadLag: 500, delay: 0, linger: 150 }
});
export function Users() {
const [users, loading, error, refetch] = getUsers();
return fragment(
h('p', null, text(() => loading() ? 'Loading…' : (error() ?? ''))),
h('ul', null,
For({ each: () => users() ?? [], render: (u) => h('li', null, `${u.name} — ${u.email}`) })
),
h('button', { onClick: refetch }, 'Refetch')
);
}