A place for focused work
Use surfaces and spacing to create a clear hierarchy.
VISOMI / FRONTEND UI
Shared Tailwind recipes, native Angular and Astro controls. One visual language, with the browser doing what it does best.
37 component families
Examples use synthetic content. No data is sent or saved.
heading.classes()
<h2 appUI uiSize="lg">A clear hierarchy</h2><Heading as="h2" uiSize="lg">A clear hierarchy</Heading>import { heading } from '@visomi-stack/web-frontend-ui';
heading.classes();text.classes()
Good defaults leave room for your content.
Good defaults leave room for your content.
Supporting text stays readable in both themes.
Good defaults leave room for your content.
<p appUI uiSize="md">Good defaults.</p>
<p appUI tone="muted" uiSize="sm">Supporting text.</p><Text uiSize="md">Good defaults.</Text>
<Text tone="muted" uiSize="sm">Supporting text.</Text>import { text } from '@visomi-stack/web-frontend-ui';
text.classes();code.classes()
button.classes()const ready = true;
button.classes({ variant: 'primary', size: 'md' });<code appUI>button.classes()</code>
<pre><code appUI block>const ready = true;</code></pre><Code>button.classes()</Code>
<pre><Code block>const ready = true;</Code></pre>import { code } from '@visomi-stack/web-frontend-ui';
code.classes();link.classes()
<a appUI href="#forms">Learn more</a>
<a appUI variant="muted" underline="hover" href="#forms">Learn more</a>
<a appUI variant="inverse" href="#forms">Learn more</a><Link href="#forms">Learn more</Link>
<Link variant="muted" underline="hover" href="#forms">Learn more</Link>
<Link variant="inverse" href="#forms">Learn more</Link>import { link } from '@visomi-stack/web-frontend-ui';
link.classes();divider.classes()
<hr appUI />
<hr appUI soft /><Divider />
<Divider soft />import { divider } from '@visomi-stack/web-frontend-ui';
divider.classes();container.classes()
Content stays centered within a standard Tailwind width.
<div appUI="container" width="sm">Content</div><Container width="sm">Content</Container>import { container } from '@visomi-stack/web-frontend-ui';
container.classes();card.classes()
Use surfaces and spacing to create a clear hierarchy.
Use surfaces and spacing to create a clear hierarchy.
Use surfaces and spacing to create a clear hierarchy.
Use surfaces and spacing to create a clear hierarchy.
Use surfaces and spacing to create a clear hierarchy.
Transparent surface
<article appUI tone="raised" padding="md">Content</article><Card tone="raised" padding="md">Content</Card>import { card } from '@visomi-stack/web-frontend-ui';
card.classes();pageHeader.classes()
A reusable heading and action layout.
<header appUI><h2>Workspace settings</h2><button appUI>Save</button></header><PageHeader><Heading>Workspace settings</Heading><Button>Save</Button></PageHeader>import { pageHeader } from '@visomi-stack/web-frontend-ui';
pageHeader.classes();appShell.classes()
Desktop sidebar, mobile bottom navigation. Native links, shared styles, no layout runtime.
Open the full layout<app-shell contentId="main" skipLabel="Skip to content">
<app-sidebar label="Primary">...</app-sidebar>
<header app-topbar>...</header>
<h1>Workspace</h1>
<nav appUI="navbar" aria-label="Primary">...</nav>
</app-shell><AppShell contentId="main" skipLabel="Skip to content">
<Sidebar slot="sidebar" label="Primary">...</Sidebar>
<Topbar slot="header">...</Topbar>
<h1>Workspace</h1>
<Navbar slot="mobile" aria-label="Primary">...</Navbar>
</AppShell>import { appShell } from '@visomi-stack/web-frontend-ui';
appShell.classes();topbar.classes()
<header app-topbar>
<span data-slot="title">Workspace</span>
<button appUI>Save</button>
</header><Topbar>
<span slot="title">Workspace</span>
<Button>Save</Button>
</Topbar>import { topbar } from '@visomi-stack/web-frontend-ui';
topbar.classes();badge.classes()
<span appUI="badge" tone="success">Ready</span><Badge tone="success">Ready</Badge>import { badge } from '@visomi-stack/web-frontend-ui';
badge.classes();avatar.classes()
<span appUI="avatar" uiSize="md" role="img" aria-label="Morgan Lee">ML</span>
<img appUI src="/favicon.svg" alt="Demo workspace" /><Avatar label="Morgan Lee" uiSize="md">ML</Avatar>
<Avatar src="/favicon.svg" label="Demo workspace" />import { avatar } from '@visomi-stack/web-frontend-ui';
avatar.classes();table.classes()
Rows become labelled cards below the md breakpoint. Column headers remain available to assistive technology.
| Workspace | Status |
|---|---|
| Studio | Ready |
| Sandbox | Draft |
<table appUI mobileCards stickyHeaders role="table"><caption>Workspaces</caption>…</table>
<!-- Keep native header associations and explicit row/cell roles. Cell labels use <span data-slot="label" aria-hidden="true">…</span>. --><Table mobileCards stickyHeaders role="table"><caption>Workspaces</caption>…</Table>
<!-- Keep native header associations and explicit row/cell roles. Cell labels use <span data-slot="label" aria-hidden="true">…</span>. -->import { table } from '@visomi-stack/web-frontend-ui';
table.classes();descriptionList.classes()
<dl appUI><dt>Plan</dt><dd>Team</dd></dl><DescriptionList><dt>Plan</dt><dd>Team</dd></DescriptionList>import { descriptionList } from '@visomi-stack/web-frontend-ui';
descriptionList.classes();pagination.classes()
<nav appUI="pagination" aria-label="Pages"><a appUI="button" href="?page=2" aria-current="page">2</a></nav><Pagination aria-label="Pages"><LinkButton href="?page=2" aria-current="page">2</LinkButton></Pagination>import { pagination } from '@visomi-stack/web-frontend-ui';
pagination.classes();fieldset.classes()
<fieldset appUI><legend appUI>Preferences</legend><label appUI inline><input appUI type="checkbox" /> Updates</label></fieldset><Fieldset><Label as="legend">Preferences</Label><Label inline><Checkbox /> Updates</Label></Fieldset>import { fieldset } from '@visomi-stack/web-frontend-ui';
fieldset.classes();field.classes()
Visible to your example workspace.
<app-field #nameField="appField" controlId="name" label="Name" description="Visible name."><input appUI [id]="nameField.controlId()" [attr.aria-describedby]="nameField.describedBy()" /></app-field><Field controlId="name" label="Name" description="Visible name."><Input id="name" aria-describedby="name-help" /></Field>import { field } from '@visomi-stack/web-frontend-ui';
field.classes();label.classes()
<label appUI for="label-example">Name</label><input appUI id="label-example" /><Label for="label-example">Name</Label><Input id="label-example" />import { label } from '@visomi-stack/web-frontend-ui';
label.classes();description.classes()
Use a synthetic address for this example.
<input appUI id="email" aria-label="Email" aria-describedby="email-help" /><p appUI="description" id="email-help">Use an example address.</p><Input id="email" aria-label="Email" aria-describedby="email-help" /><Description id="email-help">Use an example address.</Description>import { description } from '@visomi-stack/web-frontend-ui';
description.classes();errorMessage.classes()
Enter a valid email address.
<input appUI id="email" aria-label="Email" aria-invalid="true" aria-describedby="email-error" /><p app-error id="email-error">Enter a valid address.</p><Input id="email" aria-label="Email" aria-invalid="true" aria-describedby="email-error" /><ErrorMessage id="email-error">Enter a valid address.</ErrorMessage>import { errorMessage } from '@visomi-stack/web-frontend-ui';
errorMessage.classes();input.classes()
Errors expand after leaving an invalid field or checking the form. They collapse while editing and when valid.
Use a synthetic address for this example.
Enter a valid email address.
<input appUI type="email" uiSize="sm" [size]="24" required /><Input type="email" uiSize="sm" size={24} required />import { input } from '@visomi-stack/web-frontend-ui';
input.classes();textarea.classes()
Write a short example message.
<textarea appUI uiSize="md" id="message" rows="3" aria-label="Message"></textarea><Textarea uiSize="md" id="message" rows={3} aria-label="Message">Example text.</Textarea>import { textarea } from '@visomi-stack/web-frontend-ui';
textarea.classes();select.classes()
<select appUI uiSize="md" aria-label="Region"><option value="am">Americas</option></select><Select uiSize="md" aria-label="Region"><option value="am" selected>Americas</option></Select>import { select } from '@visomi-stack/web-frontend-ui';
select.classes();checkbox.classes()
<label appUI inline><input appUI type="checkbox" name="updates" /> Receive updates</label><Label inline><Checkbox name="updates" /> Receive updates</Label>import { checkbox } from '@visomi-stack/web-frontend-ui';
checkbox.classes();radio.classes()
<fieldset appUI><legend>Workspace type</legend><label appUI inline><input appUI type="radio" name="kind" value="personal" /> Personal</label></fieldset><Fieldset><legend>Workspace type</legend><Label inline><Radio name="kind" value="personal" /> Personal</Label></Fieldset>import { radio } from '@visomi-stack/web-frontend-ui';
radio.classes();toggle.classes()
<label appUI inline><input appUI="switch" type="checkbox" /> Enable notifications</label><Label inline><Toggle /> Enable notifications</Label>import { toggle } from '@visomi-stack/web-frontend-ui';
toggle.classes();radioCard.classes()
<label appUI="radio-card"><input appUI type="radio" name="plan" value="standard" /><span>Standard</span></label><RadioCard><Radio name="plan" value="standard" /><span>Standard</span></RadioCard>import { radioCard } from '@visomi-stack/web-frontend-ui';
radioCard.classes();inputGroup.classes()
<div appUI="input-group"><span data-slot="prefix" aria-hidden="true">$</span><input appUI aria-label="Amount" /></div><InputGroup><span data-slot="prefix" aria-hidden="true">$</span><Input aria-label="Amount" /></InputGroup>import { inputGroup } from '@visomi-stack/web-frontend-ui';
inputGroup.classes();alert.classes()
Status is expressed with a visible label, not color alone.
Status is expressed with a visible label, not color alone.
Status is expressed with a visible label, not color alone.
<div appUI="alert" tone="success" role="status"><strong>Ready</strong><p>Visible feedback.</p></div><Alert tone="success" role="status"><strong>Ready</strong><p>Visible feedback.</p></Alert>import { alert } from '@visomi-stack/web-frontend-ui';
alert.classes();loader.classes()
<span role="status"><span appUI="loader" aria-hidden="true"></span><span class="sr-only">Loading</span></span><Loader label="Loading example" />import { loader } from '@visomi-stack/web-frontend-ui';
loader.classes();disclosure.classes()
The typed class recipes are shared. Each application keeps native markup, events and form behavior.
<details appUI><summary>What is shared?</summary><p>The class recipes.</p></details><Disclosure summary="What is shared?"><p>The class recipes.</p></Disclosure>import { disclosure } from '@visomi-stack/web-frontend-ui';
disclosure.classes();dialog.classes()
<button appUI (click)="example.showModal()">Open dialog</button>
<dialog appUI #example="appUIDialog" aria-labelledby="dialog-title"><h2 id="dialog-title">Review</h2><form method="dialog"><button appUI type="submit">Close</button></form></dialog><Button disabled data-ui-dialog-open="example-dialog">Open dialog</Button>
<Dialog id="example-dialog" aria-labelledby="dialog-title"><h2 id="dialog-title">Review</h2><form method="dialog"><Button type="submit">Close</Button></form></Dialog>
<!-- The docs enhancement script calls showModal(). -->import { dialog } from '@visomi-stack/web-frontend-ui';
dialog.classes();