fidelity-ui
v0.1.1
Published
Wireframe, sketch and skeleton views generated from your real markup. A reveal that melts the wireframe into the final design, self-shaped loading skeletons, and a compare slider.
Maintainers
Readme
fidelity-ui
Wireframe, sketch and skeleton views generated from your real markup. There is nothing to draw by hand: the package copies the content, keeps its layout, and repaints it at a lower fidelity. Text becomes bars, images become crossed boxes, buttons become outlines, and headings and controls get small tags.
<fidelity-reveal>melts the wireframe into the real content on its own, when the element scrolls into view or when its data has loaded.<fidelity-skeleton>covers content with a skeleton of itself while it loads, so the placeholder always matches what will appear.<fidelity-compare>puts a draggable split between the wireframe and the real content, for case studies.
Framework-free custom elements, no dependencies, ~6.5 kB minified and gzipped. They work anywhere HTML does: plain pages, Astro, React 19, Vue, Svelte.
Install
npm install fidelity-uiimport { define } from 'fidelity-ui';
define(); // registers the three elements; safe to call twice, a no-op on the serverOr straight from a CDN:
<script type="module">
import { define } from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/index.js';
define();
</script>Your markup is rendered as usual, and the low-fidelity copy is added on the client. If JavaScript doesn't load, people just see the real content.
<fidelity-reveal>
<fidelity-reveal mode="wireframe">
<section>…any markup: a hero, a card, a whole page section…</section>
</fidelity-reveal>Stagger a list with delay:
<fidelity-reveal mode="sketch" delay="0">…</fidelity-reveal>
<fidelity-reveal mode="sketch" delay="180">…</fidelity-reveal>
<fidelity-reveal mode="sketch" delay="360">…</fidelity-reveal>With trigger="load", the copy stays while you fetch. Render the component with placeholder data, then remove loading when the real data is in. The copy repaints as the data arrives, so the final layout is what melts in.
<fidelity-reveal trigger="load" loading mode="skeleton">
<profile-card>…</profile-card>
</fidelity-reveal>const el = document.querySelector('fidelity-reveal');
await loadProfile();
el.loading = false; // sweeps the skeleton away| Attribute | Default | |
|---|---|---|
| mode | wireframe | wireframe, sketch (hand-drawn wobble) or skeleton. |
| trigger | view | view: when 30% of it is visible. load: covered while loading is set, revealed when it's removed. manual: call reveal(). |
| direction | ltr | ltr, rtl, ttb or btt. |
| duration / delay | 1100 / 0 | In milliseconds. |
| replay | off | Covers again when it leaves the viewport and reveals again on return. |
| line | off | Shows a thin line in the middle of the soft edge. |
| no-tags | off | Hides the H1 / Button / Link tags inside the copy. |
- Methods:
reveal()andconceal()sweep the copy away and back (both return a promise);cover()puts it back instantly. - Event:
fidelity-revealed.
<fidelity-skeleton>
<fidelity-skeleton loading>
<article>…the component rendered with placeholder or previous data…</article>
</fidelity-skeleton>While loading is set, the content is covered by a pulsing skeleton built from its own layout and the element has aria-busy="true". Remove the attribute (or set el.loading = false) and the skeleton fades out. Any mode works.
<fidelity-compare>
<fidelity-compare mode="wireframe" value="50" intro>
<section>…</section>
</fidelity-compare>| Attribute | Default | |
|---|---|---|
| mode | wireframe | wireframe or sketch. |
| value | 50 | Split in percent; 0 shows only the real content. |
| intro | off | Glides from 85 to value when it scrolls into view. |
| start-label / end-label | mode name / Final | The corner tags. hide-labels hides them. |
| label | "Compare wireframe and final design" | Accessible name of the slider. |
Properties and methods: value, glideTo(n) and refresh(). It fires a fidelity-change event.
Theming
Set these custom properties on .fl-root (the copy): --fl-paper, --fl-ink, --fl-line and --fl-fill. Set --fl-handle and --fl-focus on <fidelity-compare>.
Accessibility
- Screen readers: the copy is
aria-hiddenandinert, so assistive tech only reads the real content. - Forms: controls in the copy are disabled and lose their
name, so the copy never submits, validates or joins your radio groups. - Loading state:
<fidelity-skeleton>and<fidelity-reveal trigger="load">setaria-busywhile loading. - Compare slider: it is a native range input with
aria-valuetext("Wireframe 40%, final 60%"). When keyboard focus lands on a link under the copy, the split glides away so the focused element is visible. - Reduced motion: content appears without the sweep, and skeletons stop pulsing.
- Forced colors: in forced-colors mode the handle and lines use system colors.
Without custom elements
import { paintLofi, LofiController } from 'fidelity-ui';
paintLofi(layer, [sourceElement], { mode: 'sketch' }); // one-off copy into `layer`
const lofi = new LofiController(host, () => ({ mode: 'wireframe' })); // keeps a layer in `host` repainted
lofi.start();Position the layer over the sources yourself.
Limits
- Measurement: the copy is measured when it's painted. It repaints on resize, on content changes and once web fonts load.
- Selectors: the copy sits one level deeper than your elements. Child-combinator selectors that start outside the element (
.parent > .child) won't match it; class, tag and attribute selectors do. - IDs: the copy keeps them, so ID-based CSS still applies. The originals come first in the document, so
getElementByIdand#anchorlinks keep pointing at the real elements. - Nested custom elements: they are cloned too, so their constructors run again.
- Placeholder colors: image placeholders are data URIs and keep the default colors when you theme the copy.
Develop
npm install
npm test # vitest + happy-dom
npm run demo # http://127.0.0.1:5173/demo/
npm run media # re-records the README animations (headless Chrome + ffmpeg, animated WebP)License
MIT © Görkem Yıldız
