Skip to content
Palette Inherited colour
Download ZIP

Inherited colour palette · 5.7 KB ZIP File receipt View as Markdown View code

Preview

Fit to the available width. The frame follows the height of its content; previews taller than the maximum auto-height scroll inside it.

Give this component to your coding agent Copy a prompt that fetches this exact version and palette through the PageSugar MCP server.
cmp_brand_lockup_01 · version 1.0.0 · Inherited colour palette
Using the PageSugar MCP server, fetch component cmp_brand_lockup_01 version 1.0.0 with variant "default", first inspect its requirements and license status and confirm this project uses Svelte 5 and Tailwind CSS 4. Retrieve every manifest file, including binary assets and any manifest-only response files, preserving relative paths. Then integrate the source and follow its usage notes. Run project checks, review the browser result and report anything unverified. Do not substitute another version or invent missing files.

Not connected yet? Set up the MCP server

Code

Palette
Inherited colour
Version
1.0.0
Digest
Full digest
sha256-bc60e81232fa9868cb1077349c43199225d70b1371fc1fd9e48d02606f6bfd48
BrandLockup.svelte Svelte · 7.0 KB Raw
<script lang="ts" module>
	import type { Snippet } from 'svelte';

	/** A logo file. Width and height are its intrinsic size, used only for its aspect ratio. */
	export interface BrandLogoImage {
		src: string;
		width: number;
		height: number;
	}

	export type BrandLockupSize = 'sm' | 'md' | 'lg';
	export type BrandLockupOrientation = 'horizontal' | 'stacked';
	export type BrandLockupLogo = BrandLogoImage | Snippet;
</script>

<script lang="ts">
	interface Props {
		/** Brand name: the wordmark and the accessible name. */
		name: string;
		/** The mark: an image with its intrinsic size, or a snippet holding an inline SVG. */
		logo?: BrandLockupLogo;
		/** Destination, usually "/". Set, the whole lockup is one link. */
		href?: string;
		/** Mark beside the name, or above it. */
		orientation?: BrandLockupOrientation;
		/** Scales the mark, the gap and the type together. */
		size?: BrandLockupSize;
		/** Show the name beside the mark. False keeps it for assistive technology only. */
		showWordmark?: boolean;
		/** Short line set under the name. */
		tagline?: string;
		/** Stacked only: line everything up on the start edge, or centre it. */
		align?: 'start' | 'center';
		/** Words read after the name when linked, such as "home" ("Halcyon home"). Never shown. */
		linkSuffix?: string;
		/** Extra classes on the root element, for margins and placement. */
		class?: string;
	}

	let {
		name,
		logo,
		href,
		orientation = 'horizontal',
		size = 'md',
		showWordmark = true,
		tagline,
		align = 'start',
		linkSuffix,
		class: className
	}: Props = $props();

	const uid = $props.id();

	/*
	 * One ratio per step. A single line of the name, with its padding, is exactly as tall as the
	 * mark, so the mark centres on the capitals (Inter's line box centre sits at its cap-height
	 * middle) and stays level with the first line when a long name wraps. The padding, not the
	 * leading, makes up the height, so wrapped lines keep a reading leading; the larger taglines
	 * pull up 4 px past it. The gap is about a third of the mark. Complete class names only.
	 */
	const STEPS = {
		sm: {
			mark: 'h-6',
			name: 'text-base leading-6 tracking-[-0.011em]',
			tagline: 'text-xs leading-4',
			gap: 'gap-2'
		},
		md: {
			mark: 'h-8',
			name: 'text-lg leading-6 py-1 tracking-[-0.014em]',
			tagline: 'text-[0.8125rem] leading-4 -mt-1',
			gap: 'gap-3'
		},
		lg: {
			mark: 'h-10',
			name: 'text-2xl leading-8 py-1 tracking-[-0.019em]',
			tagline: 'text-sm leading-5 -mt-1',
			gap: 'gap-4'
		}
	} as const;

	const step = $derived(STEPS[size] ?? STEPS.md);
	const stacked = $derived(orientation === 'stacked');
	const centred = $derived(stacked && align === 'center');
	const image = $derived(typeof logo === 'object' && logo !== null ? logo : undefined);
	const snippet = $derived(typeof logo === 'function' ? logo : undefined);
	const hasMark = $derived(Boolean(image || snippet));
	// Without a mark the name has to show, or the lockup would render nothing visible.
	const wordmarkVisible = $derived(showWordmark || !hasMark);
	const taglineVisible = $derived(Boolean(tagline) && wordmarkVisible);
	const taglineId = `${uid}-tagline`;
	const suffix = $derived(linkSuffix?.trim());

	const layoutClass = $derived([
		'flex max-w-full min-w-0',
		step.gap,
		stacked ? 'flex-col' : 'flex-row items-start',
		centred ? 'items-center text-center' : stacked && 'items-start text-start'
	]);
</script>

{#snippet body()}
	<span class={layoutClass}>
		{#if hasMark}
			<span
				aria-hidden="true"
				class={['brand-lockup__mark flex max-w-full shrink-0 items-center', step.mark]}
			>
				{#if image}
					<img
						src={image.src}
						width={image.width}
						height={image.height}
						alt=""
						class="block h-full w-auto max-w-full object-contain"
					/>
				{:else if snippet}
					{@render snippet()}
				{/if}
			</span>
		{/if}
		{#if wordmarkVisible}
			<span class={['flex min-w-0 flex-col', centred ? 'items-center' : 'items-start']}>
				<span
					class={[
						'brand-lockup__name font-semibold text-balance [overflow-wrap:anywhere] text-(--_ink)',
						step.name,
						href &&
							'transition-colors duration-150 ease-(--_ease) group-hover:text-(--_ink-hover) motion-reduce:transition-none'
					]}
				>
					{name}{#if href && suffix}<span class="sr-only">{` ${suffix}`}</span>{/if}
				</span>
				{#if taglineVisible}
					<span
						id={href ? taglineId : undefined}
						aria-hidden={href ? 'true' : undefined}
						class={[
							'brand-lockup__tagline font-normal text-pretty [overflow-wrap:anywhere] text-(--_muted)',
							step.tagline
						]}
					>
						{tagline}
					</span>
				{/if}
			</span>
		{:else}
			<span class="sr-only">{href && suffix ? `${name} ${suffix}` : name}</span>
		{/if}
	</span>
{/snippet}

{#if href}
	<!--
		One link around the whole lockup: one tab stop, one focus ring. The tagline is hidden from
		the link's name and given as its description instead, so the name is the brand exactly once.
	-->
	<a
		{href}
		aria-describedby={taglineVisible ? taglineId : undefined}
		class={[
			'brand-lockup group inline-flex max-w-full min-w-11 items-center rounded-md align-middle text-(--_ink) outline-offset-4 transition-[scale] duration-80 ease-(--_ease) focus-visible:outline-2 focus-visible:outline-(--_accent) active:scale-[.98] motion-reduce:transition-none motion-reduce:active:scale-100',
			// A 44 px target however small the mark; the lockup centres inside it, held to the start.
			'min-h-11',
			className
		]}
	>
		{@render body()}
	</a>
{:else}
	<div
		class={['brand-lockup inline-flex max-w-full min-w-0 align-middle text-(--_ink)', className]}
	>
		{@render body()}
	</div>
{/if}

<style>
	/*
	 * Public tokens: set --brand-lockup-* on the lockup or any ancestor. By default the name takes
	 * the surrounding text colour and the tagline a softer mix of it, so one lockup sits on a white
	 * header, a dark footer band or a tinted page without a change.
	 */
	.brand-lockup {
		--_accent: var(--brand-lockup-accent, currentColor);
		--_ink: var(--brand-lockup-ink, currentColor);
		--_muted: var(--brand-lockup-muted, color-mix(in srgb, currentColor 66%, transparent));
		--_ink-hover: color-mix(in srgb, var(--_ink) 72%, transparent);
		--_ease: cubic-bezier(0.2, 0, 0, 1);
	}

	/*
	 * A snippet mark is consumer markup: an inline SVG fills the mark's height and keeps its own
	 * aspect ratio. Layered and wrapped in :where() so a class on the consumer's SVG still wins.
	 */
	@layer components {
		.brand-lockup__mark :global(:where(svg)) {
			display: block;
			height: 100%;
			width: auto;
			max-width: 100%;
		}
	}

	/* Arabic, Hebrew and other right-to-left scripts are never letter-spaced. */
	.brand-lockup__name:dir(rtl) {
		letter-spacing: 0;
	}

	/* Chinese, Japanese and Korean: no Latin tracking, and the name breaks at phrases. */
	.brand-lockup__name:lang(zh),
	.brand-lockup__name:lang(ja),
	.brand-lockup__name:lang(ko) {
		letter-spacing: 0;
		word-break: keep-all;
		line-break: strict;
	}

	@supports (word-break: auto-phrase) {
		.brand-lockup__name:lang(ja),
		.brand-lockup__tagline:lang(ja) {
			word-break: auto-phrase;
		}
	}
</style>

Pass the brand name and, usually, a logo (an image with its intrinsic width and height, or a snippet holding an inline SVG) and an href. It renders one link or a plain element holding the mark, the name and an optional tagline. It does not ship a logo, pick a light or dark logo file for you, enforce clear space beyond its fixed gap, or shorten a long name.

Suggested location
src/lib/components/brand-lockup-01
Required props
name

Limitations

  • Light logo files only: there is no darkLogo prop. On a dark band, use a snippet mark drawn in currentColor, or swap the image yourself.
  • The gap between mark and name is fixed per size; brand clear-space rules beyond it are the consumer's to apply around the lockup.
  • A long name wraps; it is never shortened or truncated. If a header has no room for it, hide the wordmark at that width or pass a shorter name.
  • The vertical centring of mark and name is tuned for Inter-like fonts, whose line box centre sits at cap-height middle. Fonts with unusual metrics may want the mark nudged.
  • The name inherits font-sans. A brand with its own display face sets the font on the lockup or an ancestor.

Example

Svelte
<script lang="ts">
	import BrandLockup from '$lib/components/brand-lockup-01/BrandLockup.svelte';
</script>

<!-- A site header: mark and name, linked home. -->
<BrandLockup name="Halcyon" href="/" logo={{ src: '/logo-mark.svg', width: 32, height: 32 }} />

<!-- A sign-in card: stacked, large and centred, with a tagline. -->
<BrandLockup
	name="Halcyon"
	href="/"
	logo={{ src: '/logo-mark.svg', width: 32, height: 32 }}
	orientation="stacked"
	size="lg"
	align="center"
	tagline="Roadmaps and timelines for teams"
/>

Brand lockup#

The site's mark and name as one unit. Give it the name, a logo and, in a header, href="/".

Svelte
<script lang="ts">
	import BrandLockup from '$lib/components/brand-lockup-01/BrandLockup.svelte';
</script>

<BrandLockup name="Halcyon" href="/" logo={{ src: '/logo-mark.svg', width: 32, height: 32 }} />

Sizes#

Size Mark Name Gap Tagline Where
sm 24 px 16 px 8 px 12 px footers, dense app bars
md 32 px 18 px 12 px 13 px site headers (the default)
lg 40 px 24 px 16 px 14 px sign-in screens, stacked lockups

The mark is as tall as the name's line, so the two centre on the capitals. A logo image's width and height are its own intrinsic size; the component sets the height and lets the width follow, so a 4:1 logo stays 4:1.

An inline SVG mark#

Pass a snippet when the mark should follow the text colour, on a dark footer band say. (Hover retones only the name; the mark stays as it is.)

Svelte
{#snippet mark()}
	<svg viewBox="0 0 32 32" fill="currentColor">
		<rect width="32" height="32" rx="8" />
	</svg>
{/snippet}

<BrandLockup name="Halcyon" href="/" logo={mark} />

The SVG fills the mark's height and keeps its ratio. The component hides it from assistive technology, so don't give it a <title>.

Mark only#

showWordmark={false} hides the name visually and keeps it in the markup. Add linkSuffix="home" if you want the link read as "Halcyon home". For a mark-only lockup on phones and a full one above, render both and hide one at each width:

Svelte
<BrandLockup class="sm:hidden" name="Halcyon" href="/" {logo} showWordmark={false} />
<BrandLockup class="max-sm:hidden" name="Halcyon" href="/" {logo} />

Dark bands#

The name takes the surrounding text colour, so set the band's colour and leave the tokens alone. A logo image keeps its own colours; use a snippet in currentColor or a light file.

Props and content inputs#

On this page
NameTypeRequiredDefaultDescription
namestringYesNoneBrand name: the wordmark and the accessible name. Never truncated.
hrefstringNoNoneDestination, usually "/". Set, the whole lockup is one link with one focus ring and a 44 px minimum height.
orientation'horizontal' | 'stacked'No'horizontal'Mark beside the name, or above it.
size'sm' | 'md' | 'lg'No'md'Mark height 24, 32 or 40 px with a 16, 18 or 24 px name; the gap scales with them.
showWordmarkbooleanNotrueFalse hides the name and tagline visually and keeps the name for assistive technology. Ignored when there is no logo.
taglinestringNoNoneShort line set small and muted under the name. When linked it describes the link rather than joining its name.
align'start' | 'center'No'start'Stacked only: line the mark, name and tagline up on the start edge, or centre them.
linkSuffixstringNoNoneWords read after the name when linked, such as "home", giving "Halcyon home". Never shown.
classstringNoNoneExtra classes on the root element, for margins and placement.

Customization#

On this page

Size, orientation and alignment through props; the name, tagline and focus ring follow the surrounding text colour unless --brand-lockup-ink, --brand-lockup-muted or --brand-lockup-accent are set.

  • Colour: by default the name is the surrounding text colour and the tagline a 66% mix of it, so the lockup works on a white header, a dark footer band and a tinted page. Set --brand-lockup-ink for a brand-coloured name and --brand-lockup-muted for the tagline.
  • Dark or tinted page: set the container's text colour (text-zinc-50 on bg-zinc-950, say) and draw the mark as an inline SVG snippet in currentColor so it follows too. A logo image does not change colour; swap the file for a light version.
  • Focus ring: it is the text colour by default; set --brand-lockup-accent to your accent. Keep it at 3:1 against the background.
  • Logo files: pass the image's own width and height, not the size you want. The component sets the height from size and lets the width follow.
  • A logo that already contains the name (a wide 4:1 lockup image): pass it as logo with showWordmark={false}; the name stays for screen readers.
  • Sizes: the STEPS map at the top of the script holds each step's mark height, type and gap as complete classes. Change a step there and keep the mark as tall as the name's line height so they stay centred.
  • Responsive: for a mark-only lockup on phones, render two lockups and hide one with sm:hidden / max-sm:hidden, or pass showWordmark from your own media query state.

Public CSS variables

VariableToken
--brand-lockup-accentaccent
--brand-lockup-inkink
--brand-lockup-mutedmuted

Accessibility#

On this page
  • When linked, the lockup is one anchor: one tab stop and one focus ring around the mark, name and tagline. Its accessible name is the brand name exactly once, plus linkSuffix if you set it.
  • The mark is always aria-hidden and a logo image has alt="", because the name is already there in text; a screen reader never hears "Halcyon Halcyon".
  • With showWordmark={false} the name stays in the markup as visually hidden text, so a mark-only lockup still has a name.
  • A linked tagline is hidden from the link's name and attached as its description (aria-describedby), so the name stays short and exact.
  • The link is at least 44 px tall at every size. The focus ring is a 2 px outline, 4 px out, on :focus-visible only, in --brand-lockup-accent (the text colour by default).
  • Contrast follows the page: the name is the surrounding text colour and the tagline a 66% mix of it (about 5.9:1 for zinc-950 on white). Re-check if you set the tokens or put the lockup on a mid-tone fill.
  • A logo image must be legible on its background; the component cannot check that.

Known limitations

  • No dark logo variant: a logo image keeps its own colours on a dark band.
  • Hover changes only the name's tone; an image mark does not change.

Release details#

On this page
Integration
  • Presentational
  • Works without client-side JavaScript
  • Server-side rendering supported
Dependencies
No additional runtime packages beyond Svelte and Tailwind CSS
License

MIT. Default license approval is pending; see the license status before adopting the source.

Version history
  • 1.0.0 (Published) Current release · 1 October 2026

Only the current release is available. Keep downloaded source and its receipt if you need to use it again later.