HabLogo
The LiveReader brand marks. Reach for it anywhere the product signs its own name — a nav rail, a sign-in screen, an empty state.
Each mark is two stroked paths, so the two can be coloured separately, drawn on when the mark appears, and interpolated into any other mark when name changes.
livereadernotitiaalternisnova<HabLogo name="livereader" :size="48" label="LiveReader" />Morphing
Changing name interpolates the geometry rather than swapping one file for another. The paths are matched up by arc position and subdivided to a common segment count first, so the two ends of the animation are the real marks — not approximations of them.
Interrupting a morph is fine: picking a third mark part-way through continues from whatever is currently on screen.
<HabLogo :name="current" :size="128" :duration="900" />Appearing and disappearing
The mark draws itself on, second path trailing the first. show runs the same animation in reverse and leaves the element in place, so it can be toggled back.
livereadernotitiaalternisnova<HabLogo name="livereader" :show="visible" :duration="1200" />Set :animate="false" to opt out entirely — worth doing for a mark in a header that would otherwise redraw on every navigation.
Colours
color paints the first path and accent the second; leave accent unset and both follow color. The default is currentColor, so a bare <HabLogo> picks up the surrounding text colour and needs no configuration to work in light and dark. background fills the square behind the mark and is transparent by default — no rect is rendered at all unless you ask for one.
<HabLogo name="notitia" color="var(--hab-accent)" accent="var(--hab-fg-3)" />Sizing
size takes a named step — sm 24, md 40, lg 64, xl 96 — or a raw pixel number for the cases a ladder cannot serve, such as a 16px favicon or a 512px splash.
Those numbers come from no token. --hab-icon-* stops at 24 and --hab-control-* describes control heights; neither describes a brand mark, so the ladder is currently HabLogo's own and is listed in the README's TO DISCUSS.
<HabLogo name="nova" size="lg" />
<HabLogo name="nova" :size="512" />The value is written to the SVG's width and height attributes rather than inline styles, which are the lowest-priority way to size an SVG. A utility class therefore still wins:
<HabLogo name="nova" class="size-16" />Exporting a file
habRenderLogoSvg returns a standalone SVG document as a string, for favicons, e-mail templates, or anywhere that needs a file rather than a component. It reads the same geometry the component draws, so an export cannot drift from what is on screen.
import { habRenderLogoSvg } from "@livereader/habitus-vue";
const svg = habRenderLogoSvg({
name: "livereader",
size: 512,
color: "#18181b",
background: "#ffffff",
});width and height are always written out, not just viewBox — a sizeless SVG rasterizes at an arbitrary intrinsic size in some browsers, which is exactly what a PNG export runs into. The gallery's SVG and PNG buttons above are live and use exactly this: render the SVG, draw it to a canvas at the requested size, save it.
HAB_LOGO_NAMES is the list of marks, for building a picker without hard-coding the names:
<script setup lang="ts">
import { HAB_LOGO_NAMES } from "@livereader/habitus-vue";
</script>
<template>
<HabLogo
v-for="name in HAB_LOGO_NAMES"
:key="name"
:name="name"
/>
</template>About the artwork
The marks are committed as path data rather than .svg assets, because morphing needs the control points rather than an opaque file.
Their stroke width was normalized on the way in. alternis and nova were exported with one path under an anisotropic squash, which renders a stroke varying from 94.87 to 47.43 units depending on direction; every other path across all four marks is exactly 75. That reads as an export artifact rather than design intent, so the family was unified at 75 — a decision still awaiting a designer's confirmation, tracked in the README's TO DISCUSS.
Accessibility
- Without
labelthe mark isaria-hidden, which is right when it sits beside a wordmark or inside an already-labelled link — otherwise screen readers announce the same thing twice. - With
labelit becomesrole="img"with a<title>wired up byaria-labelledby. prefers-reduced-motion: reducedisables both the draw and the morph; changes apply instantly instead.- The mark renders fully drawn on the server, so it is visible without JavaScript rather than stuck at zero opacity.
Your part: pass label when the mark is the only thing identifying what it labels — a bare logo link to the dashboard, a sign-in screen. Leave it off when something adjacent already says it, such as a wordmark beside the mark or a link that already has its own accessible name. A logo announced twice is worse than one announced once.
API
Props
| Prop | Type | Default | Description |
|---|---|---|---|
accent | string | undefined | Colour of the second path. Falls back to color when unset. |
animate | boolean | true | Master switch for the draw and morph animations. When false — or when the viewer has asked for reduced motion — every change applies instantly. |
background | string | undefined | Fill behind the mark. Transparent by default — no rect is rendered at all. |
color | string | "currentColor" | Colour of the first path. Any CSS colour. |
duration | number | 700 | Duration of the draw and morph animations, in milliseconds. Off the --hab-dur-* ladder deliberately: the longest token is 1.4s for a looping spinner, and the next is 0.3s, which is far too quick to read a mark being written stroke by stroke. |
label | string | undefined | Accessible name. Without one the mark is treated as decorative and hidden from assistive technology, which is right when it sits next to a wordmark or inside an already-labelled link. |
name* | "livereader" | "notitia" | "alternis" | "nova" | — | Which brand mark to render. |
show | boolean | true | Whether the mark is drawn. Setting it to false runs the draw-out animation and leaves the element in place, so it can be toggled back. |
size | number | HabLogoSize | "md" | Rendered edge length; the marks are square. Takes a named step, or a raw pixel number for the cases a ladder cannot serve — a favicon at 16, a splash screen at 512. Applied as the SVG's width and height attributes rather than inline styles, so a CSS class such as size-8 still wins. |
* required