@fikar/design
v0.2.0
Published
Fikar Ink design language — tokens, Tailwind preset + component layer. Single source of truth across all Fikar surfaces.
Maintainers
Readme
@fikar/design
The Fikar Ink design language as one importable package: CSS design tokens + a Tailwind preset. Single source of truth for every Fikar surface, so the look is consistent by construction, not by copy-paste.
Mirrors docs/design-language.md. Full rollout plan: docs/design-package-plan.md.
What's in it
| Import | What |
|---|---|
| @fikar/design/tokens.css | The --fk-* ramp + shadcn semantic roles (:root light + .dark). Plain CSS — works anywhere. |
| @fikar/design/components.css | The Ink component layer: badges/pills, brand loader + splash, empty state, buttons/inputs/card, .fk-table, app-shell + metrics classes. Requires tokens.css. |
| @fikar/design/preset | Tailwind preset (colors, fonts, radius, keyframes). Pure data, no plugins. |
| @fikar/design/brand/fikar-loader.svg | The loader's inline-SVG markup (.fkl-* classes; see the loader recipe below). |
| @fikar/design/brand/* | Fikar logomark SVGs: fikar-logo.svg (light), fikar-logo-reversed.svg (dark), fikar-logo-mono.svg (currentColor, small in-app/print). |
| @fikar/design/brand/favicon/* | Favicon / app-icon set: favicon.ico, favicon.svg, favicon-16/32/48.png, apple-touch-icon.png, icon-192/512.png. |
| @fikar/design/logo.css | Theme-aware swap classes (fk-logo--light / fk-logo--dark) for non-Tailwind consumers. |
| @fikar/design/fonts.css | Self-hosted Charter (serif) @font-face (4 styles, woff2). Enables the serif family. |
Type roles
| Role | Family | Use |
|---|---|---|
| font-sans | Inter (Google Fonts, in index.html) | UI chrome — buttons, labels, tables, controls. The default. |
| font-serif | Charter (self-hosted, fonts.css) | Headings + body prose / reading surfaces. |
| font-mono | JetBrains Mono (Google Fonts) | Numerals, IDs, code, numeric tables. |
To use Charter, @import '@fikar/design/fonts.css' in your global stylesheet, then apply font-serif
(e.g. to headings and prose). Charter is bundled (Bitstream, redistributed by Butterick — free, no
CDN). License: fonts/Charter-license.txt; BITSTREAM CHARTER is a registered trademark of Bitstream Inc.
Logo
The mark is logomark-only (no wordmark) — pair it with app text in a lockup.
// Tailwind app — theme swap via dark: variants, no extra CSS
import logoLight from '@fikar/design/brand/fikar-logo.svg';
import logoDark from '@fikar/design/brand/fikar-logo-reversed.svg';
<img src={logoLight} alt="Fikar" className="block h-5 w-auto dark:hidden" />
<img src={logoDark} alt="" aria-hidden className="hidden h-5 w-auto dark:block" />Non-Tailwind (e.g. Docusaurus): import '@fikar/design/logo.css' and use the fk-logo--light / fk-logo--dark classes. For a single-color mark that follows text color, use fikar-logo-mono.svg (inherits currentColor).
Favicon
Favicons must sit at the web root, so they can't be imported from node_modules at runtime — the
package is the canonical source; each app copies the set into its public/ (low-drift, favicons
rarely change). Copy brand/favicon/{favicon.ico,favicon.svg,favicon-16x16.png,favicon-32x32.png,apple-touch-icon.png}
into public/, then in index.html:
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />Component layer (components.css, since 0.2.0)
Plain CSS classes matching docs/mockups/fikar.css names: .badge .b-*, .pill .p-*,
.fikar-loader/.fikar-splash, .empty, .btn*/.iconbtn, .inp/.field/.hint,
.card/.panel-head/.count, .avatar/.av, .switch, .metrics/.metric/.delta,
app shell (.shell/.sidebar/.topbar/.appbar/.nav-item…), and .fk-table
(tables are class-scoped so the package never restyles a host app's tables).
Rules:
@importit aftertokens.cssand before your@tailwinddirectives. It is deliberately un-layered (Tailwind v3 intercepts@layer componentsin consumer builds); import order is what lets Tailwind utilities win ties.- Status colors ride the role vars (
--warning,--destructive…), so dark mode is automatic. --erroris not a role; the layer uses--destructive.
Brand loader recipe
The loader animates inline SVG internals, so an <img> won't work. Paste the markup
from brand/fikar-loader.svg once into a small local wrapper:
// src/components/common/fikar-loader.tsx — the one sanctioned copy-paste
export function FikarLoader({ size = '' }: { size?: '' | 'xs' | 'sm' | 'lg' }) {
return (
<span className={`fikar-loader ${size}`} role="status" aria-label="Loading">
{/* inline the <svg> from @fikar/design/brand/fikar-loader.svg here */}
</span>
);
}Inside a .btn it inherits the button's text color. Honors prefers-reduced-motion
(freezes to the fully drawn mark).
Empty state recipe
<div class="empty">
<span class="em-mark"><!-- inline fikar-logo-mono.svg --></span>
<h3>Nothing captured yet</h3>
<p>One line of orientation.</p>
<button class="btn btn-primary">One action</button>
</div>Use it — Tailwind + shadcn apps (admin, platform, chat, cost, marketing)
// tailwind.config.ts
import fikarPreset from '@fikar/design/preset';
import animate from 'tailwindcss-animate';
export default {
presets: [fikarPreset], // colors / fonts / radius / keyframes come from here now
content: ['./index.html', './src/**/*.{ts,tsx}'],
plugins: [animate], // keep the plugin local
// delete your own theme.extend.colors / fontFamily / borderRadius / keyframes
};/* src/styles/globals.css */
@import '@fikar/design/tokens.css'; /* replaces your local :root / .dark blocks */
@tailwind base;
@tailwind components;
@tailwind utilities;Then delete the now-duplicated :root/.dark variable blocks and the duplicated Tailwind theme config. Ensure Inter + JetBrains Mono are loaded in index.html (chat currently loads Fraunces/Hanken — swap those).
Use it — Docusaurus (docs-site)
Docs imports only the raw ramp and aliases Infima to it. Do not delete the derived Infima primary shades or the --ifm-* mappings — those are Infima-specific and have no other consumer.
/* src/css/custom.css */
@import '@fikar/design/tokens.css'; /* delete the hand-copied --fk-* ramp block; keep your --ifm-* aliases */
/* :root { --ifm-color-primary: hsl(var(--fk-blue600)); ... } ← these keep working unchanged */Docusaurus toggles [data-theme='dark'], not .dark, so docs keeps its own dark block (it already references --fk-*).
Tokens
- Ramp is
--fk-*(e.g.--fk-blue600,--fk-n200). Compose withhsl():hsl(var(--fk-blue600) / 0.5). - Semantic roles are unprefixed (
--primary,--background, …) per the shadcn convention; they resolve through the ramp.
Quality gates (npm test, also CI publish gate)
- parity —
tokens.cssmust reproduce every value in the canonicaldocs/mockups/fikar.css(under the--fk-rename). A fat-fingered value fails here. - contrast — core reading pairings meet WCAG-AA in both themes. (Muted text: 4.60:1 light / 6.85:1 dark.)
No build step:
tokens.cssis the source,preset.jsships as-is. No snapshot test —git diffon a hand-written token file already shows every value change.
Versioning
0.x during rollout (token churn expected); reach 1.0.0 quickly. On 0.x, caret does not auto-flow minors, so pin exact or ~ until 1.0.0. A token change = bump here, then bump the dep in each consumer (deliberate, visible).
