alt-branding
The ALT documentation brand kit in one package: the System UIcons set, product lockups, product tile icons, the brand palette and the design tokens.
It gathers what used to live scattered around: inline SVG in every component, hex values written straight into styles, two unrelated logo systems and asset folders inside the theme. The same branding is now read by the theme, the export plugin and the OG-image renderer.
📦 Install
pnpm add @ampernic/alt-brandingThe package is published to the private Forgejo registry:
@ampernic:registry=https://altlinux.space/api/packages/ampernic/npm/Node >= 18. Vue is an optional peer: the palette, the lockup resolvers and the string icon API work without it.
🎨 Entry points
Subpaths are split so a consumer only pays for what it uses.
| Import | What it holds |
|---|---|
@ampernic/alt-branding | palette, lockups, composables, components |
@ampernic/alt-branding/icons | per-icon modules, tree-shakeable |
@ampernic/alt-branding/raw | whole-set string API, for non-Vue code |
@ampernic/alt-branding/colors | palette only, no Vue |
@ampernic/alt-branding/logos | lockup sprite and resolvers |
@ampernic/alt-branding/components | Vue components only |
@ampernic/alt-branding/styles/fonts.css | @font-face rules for the brand typeface |
@ampernic/alt-branding/styles/tokens.css | design tokens as CSS variables |
@ampernic/alt-branding/styles/vitepress-icons.css | re-points VitePress's own glyphs |
@ampernic/alt-branding/styles/nolebase-icons.css | re-points the Nolebase menu glyphs |
🔤 Typeface
Montserrat, weights 400/500/600/700. The package ships the font rather than pulling it from Google Fonts: that host is slow or blocked for a large share of readers, and the brand face was silently falling back to a system sans.
import '@ampernic/alt-branding/styles/fonts.css'
import '@ampernic/alt-branding/styles/tokens.css'font-family: var(--altb-font-sans);One variable file covers every weight, and subsets are split by unicode-range, so a Russian page fetches ~24KB of Cyrillic and picks up Latin only where the text carries commands or listings.
The family registers as Montserrat rather than Montserrat Variable, so a copy installed on the reader's machine cannot win and shift the metrics.
Font files are Montserrat 5.3.0 under the SIL Open Font License 1.1; the licence travels with them in assets/fonts/OFL.txt.
🔣 Icons
430 icons, 32×32, stroke-based. Strokes are drawn in currentColor, so an icon inherits the colour around it and needs no separate per-theme treatment.
In a component
Every icon is its own module, so a bundler keeps only what is referenced.
<script setup lang="ts">
import { AltIcon } from '@ampernic/alt-branding'
import { ChevronRight, Download } from '@ampernic/alt-branding/icons'
</script>
<template>
<AltIcon :icon="ChevronRight" :size="20" />
<AltIcon :icon="Download" :size="16" label="Download" />
</template>An icon without label is marked aria-hidden, which is correct for a glyph sitting next to its own caption. Pass label when the icon is the control.
From frontmatter
When the name arrives from frontmatter or config, register the set first, then address the icon by name.
// theme entry
import { registerIcons } from '@ampernic/alt-branding'
import * as icons from '@ampernic/alt-branding/icons'
registerIcons(icons)---
productLink:
icon: chevron-right
---<AltIcon :name="frontmatter.productLink.icon" :size="20" />The registry is deliberately empty by default: preloading the whole set to serve one dynamic lookup would defeat the per-icon modules. An unregistered name renders nothing and warns in dev; it never throws.
Outside Vue
The export plugin and the OG-image renderer run at build time and take markup as a plain string.
import { iconSvg } from '@ampernic/alt-branding/raw'
iconSvg('chevron-right', { size: 20 })This entry pulls in the whole set on purpose: bundle size does not matter there. Browser code should use the per-icon modules instead.
Stroke weight
The set is drawn at a single weight, and on a small box that weight reads lighter than the text around it. The weight sits on the wrapper rather than on the paths, so it can be retuned for a given size without touching the set:
.my-inline-icon {
--alt-icon-stroke: 1.74; /* viewBox units, 1.52381 by default */
}Every rendered icon carries data-icon with its own name, which is enough to adjust a single glyph without disturbing its neighbours:
.my-link [data-icon="chevron-right"] {
margin-left: -9.2px;
}That nudge is rarely needed: the chevron sits centred in its square box and leaves noticeable air to its left, where most glyphs reach their box edges.
Re-pointing host glyphs
VitePress and the Nolebase readability menu draw their own chrome - chevrons, search, the theme switch - as a mask fed by a CSS variable. Importing the ready stylesheet moves them onto this set without touching either component:
import '@ampernic/alt-branding/styles/vitepress-icons.css'
import '@ampernic/alt-branding/styles/nolebase-icons.css'One subtlety concerns the directional glyphs. VitePress draws all four chevrons
- and all four arrows - from one glyph rotated with
transform. The stylesheet therefore hands the whole family the base glyph and leaves the rotation to the host; overriding each direction separately would rotate the icon twice.
🏷 Product lockups
Icon plus wordmark, from a shared SVG sprite, in a full-colour (flat) and a mono-invert variant.
<AltLogo logo="alt-server" />
<AltLogo logo="alt-workstation-k" :aliases="registry.productBrandingKeys" />
<AltLogo logo="alt-server" force-light />The variant follows the current theme. force-light is for print and PDF export, where a mono-invert lockup would come out white on white. Products with no lockup of their own fall back to the BaseALT one.
Height is driven by --alt-logo-height (default clamp(76px, 14vh, 128px)); width follows the wordmark.
🧩 Product tile icons
The square artwork for cards and distro landings: icon only, no wordmark.
<AltProductIcon
slug="alt-workstation-k"
:family-primary="registry.productFamilyPrimary"
/>Both theme variants are rendered and swapped in CSS: a computed isDark only settles after hydration, which would flash the wrong artwork on first paint.
Editions with no artwork of their own (-e2k, -p10, -practic) resolve to their parent product through PRODUCT_ICON_ALIASES. The same map absorbs the naming drift: frontmatter says alt-workstation-k while the file is called alt-kworkstation.
🎨 Palette
import {
BASEALT_COLORS,
productColor,
productColorRgb,
} from '@ampernic/alt-branding/colors'
BASEALT_COLORS.orange // '#ff8000'
productColor('alt-orchestra') // '#664ae3'
productColorRgb('alt-orchestra') // '102, 74, 227'productColorRgb returns the bare triple rather than an rgb() wrapper, so it can go straight into rgba(var(--accent-rgb), .12) for translucent tints. An unknown slug returns the BaseALT orange rather than throwing.
🎛 Design tokens
import '@ampernic/alt-branding/styles/tokens.css'Declares the --altb-* brand primitives and the --ds-* layout and surface tokens, with dark-theme overrides under .dark.
The --ds-* names are kept as they were so existing theme CSS keeps working untouched. New code should read the --altb-* primitives.
The --altb-font-sans and --altb-font-mono typeface tokens are declared alongside them, with --altb-weight-regular … --altb-weight-bold for weights.