Skip to content

Logo

Renders a raw inline SVG mark at a size normalized against its own aspect ratio, so a wide wordmark, a square badge, and a tall crest read as consistent side by side. With no SVG it falls back to the brand name as text.

Import

import { Logo } from "@hyzer-labs/ui"

Demo

svg takes a raw SVG string. It is usually a page-local constant from a ?raw import:

const acmeSvg =
	'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 64 190 62">…</svg>';

<Logo name="ACME Corp" svg={acmeSvg} />

Sizing a wall by equal height alone stretches a flat wordmark very wide and squeezes a tall crest down to a sliver. Neither one reads as belonging beside the others. Logo's normalization interpolates toward equal height instead of matching it exactly, so every mark lands at a comparable size with no distortion. Logo stamps its width factor inline, and an inline value cannot be overridden from outside, so the naive row below uses plain <svg> elements rather than Logo: the same fixed-height CSS you would otherwise reach for.

Naive sizing, every mark forced to the same height in plain SVG:

<div class="naive-row">
	{#each logos as logo (logo.name)}
		<span class="naive-logo">{@html logo.svg}</span>
	{/each}
</div>

<style>
	.naive-row { display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap; }
	/* :global: the svg comes from {@html}, invisible to Svelte statically. */
	.naive-logo :global(svg) { display: block; height: 4rem; width: auto; }
</style>

Normalized, Logo's default, with the per-mark scale/brightness tuning the Appearance tab covers. The same config renders every wall on this page:

const logos = [
	{ name: 'ACME Corp', svg: acmeSvg, scale: 1, brightness: 1 },
	{ name: 'Umbrella Corporation', svg: umbrellaSvg, scale: 0.9, brightness: 1 },
	{ name: 'Black Mesa Research Facility', svg: blackMesaSvg, scale: 1.15, brightness: 1 },
	{ name: 'Lumon Industries', svg: lumonSvg, scale: 1.4, brightness: 1 },
	{ name: 'Weyland-Yutani', svg: weylandSvg, scale: 1.3, brightness: 1 },
	{ name: 'Delos', svg: delosSvg, scale: 1.2, brightness: 1 }
];

<Cluster gap="lg" justify="center">
	{#each logos as logo (logo.name)}
		<Logo name={logo.name} svg={logo.svg} scale={logo.scale} brightness={logo.brightness} />
	{/each}
</Cluster>

Header's and Footer's logo snippets are both plain snippets, so a Logo drops in unchanged. The link, not the logo, owns the click target and the focus ring:

svelte
<Header>
	{#snippet logo()}
		<a href="/" aria-label="Acme home">
			<Logo name="Acme" svg={acmeSvg} />
		</a>
	{/snippet}
</Header>

<Footer>
	{#snippet logo()}
		<a href="/" aria-label="Acme home">
			<Logo name="Acme" svg={acmeSvg} />
		</a>
	{/snippet}
</Footer>

Props

NameTypeDefaultNote
namestringRequired. Names the mark for assistive technology, and is the visible text when no SVG is given.
svgstringRaw SVG markup, rendered as-is and never sanitized. Pass only a static asset you control — a `?raw` import or a build-time constant, never user input or a fetched string. Missing (or whitespace-only) renders `name` as text instead.
childrenSnippetAnother way to supply the mark: an `<svg>` written directly as markup. Logo can only measure it once it mounts in the browser, while `svg` is measured during server rendering. If both are given, `svg` wins.
scalenumber1A multiplier on top of the normalized width — the optical-weight knob. A positive number, typically 0.7-1.4.
brightnessnumber1A filter: brightness() nudge for a mark that reads too dark or too light. Written inline only when it differs from 1.
decorativebooleanfalseSet when adjacent text already names the brand — hides the mark from assistive technology entirely.
monochromebooleantrueRecolors the mark to one color that follows the surrounding text. Set to false to keep the SVG's own colors.
classstringMerged after the hz-logo class.

Theme hooks

What this component promises your CSS. The reference theme styles exactly these — from @layer hz-theme, so your unlayered rules win. See Styling Components for the how.

Root class: .hz-logo

Data attributes

HookValuesStyles
data-fallbackpresent with no svgThe text form — no injected SVG, .hz-logo-text renders the name instead.
data-monochromepresent by defaultAbsent when monochrome={false}. Gates the fill rule below, so an unset SVG keeps its own colours.

Custom properties

HookValuesStyles
--hz-logo-size<length> — default clamp(4rem, 8vw, 6rem)The normalization base every logo scales against. Override it on a wall container (Cluster/Grid) to resize every logo inside at once.
--hz-logo-brightness<number> — default 1Applied as filter: brightness(). Usually set through the brightness prop, which stamps it inline only when != 1 — the hook stays directly settable in CSS for container-wide muting.
--hz-logo-color<color> — default currentColorRead, not declared, in the monochrome fill rule — a wall mutes itself with one override, the Container --hz-breakout-shift precedent for a read-only hook.

Part classes

HookValuesStyles
.hz-logo-textchild elementThe text fallback.

Accessibility

An informative logo (the default) gets role="img" and an aria-label taken from name. Assistive technology reads one graphic with one name, however many paths or groups the SVG holds inside. Set decorative when nearby text already says the brand name; the mark is then hidden from assistive technology. With no svg, name renders as real, selectable text and needs no role at all.

References: MDN: role="img" · WAI: An alt Decision Tree