Design System

Peraa

A minimal, token-based design system I created for building clean, accessible interfaces. Yes, I created a design system and use it for my portfolio, because Design Systems is what I do. (The system is actively maintained and expanded. A standalone public release is planned.)

Getting Started

Peraa is a lightweight design system built with vanilla CSS custom properties. It provides a comprehensive token system and reusable components for building consistent, accessible user interfaces. There is also a React and Tailwind CSS implementation.

Available Platforms

Peraa is available in three formats. Choose the one that fits your project:

HTML/CSS/JS

Vanilla implementation with CSS custom properties. Zero dependencies.

React

React components with props, TypeScript support, and composable patterns.

Tailwind CSS

Tailwind config preset with utility classes and component styles.

Installation

HTML
<!-- Include the stylesheet -->
<link rel="stylesheet" href="css/main.css">

<!-- Include the JavaScript (for theme toggle, tabs) -->
<script src="js/main.js"></script>
JSX
// Import Peraa styles in your app entry
import '@peraa/css/main.css';

// Import components
import { Button, Badge, Card, Tabs } from '@peraa/react';

// Use in your components
function App() {
  return (
    <Button intent="primary">Click me</Button>
  );
}
JavaScript
// tailwind.config.js
const peraaConfig = require('@peraa/tailwind/tailwind.config');

module.exports = {
  presets: [peraaConfig],
  content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
  // your customizations...
};

// In your CSS
@import '@peraa/tailwind/components.css';

Architecture

Peraa uses a two-tier token system:

  • Primitive Tokens: Raw values (colors, spacing, typography) that form the foundation
  • Semantic Tokens: Context-aware tokens that reference primitives and adapt to themes

Theme Support

Peraa supports light and dark modes out of the box. The theme automatically respects the user's system preference and can be toggled manually.

JavaScript
// Toggle theme programmatically
window.Peraa.ThemeManager.toggle();

// Set specific theme
window.Peraa.ThemeManager.setTheme('dark');
window.Peraa.ThemeManager.setTheme('light');

Components

41 components across 7 categories, each available as HTML/CSS and React + TypeScript.

Buttons

Primary, secondary, and tertiary styles with multiple sizes.

Badges

Tags and skill badges for labeling content.

Tabs

Horizontal tab navigation with keyboard support.

Cards

Content cards with image, title, and description.

Image Placeholder

Clickable images with hover overlay effects.

Timeline

Vertical sticky timeline for case study pages.

Alert

Inline notifications with info, success, warning, and error variants.

Modal

Accessible dialog overlay with focus trapping.

Glass

Frosted-glass surface for elevated UI layers.

Navigation

Top nav bar with logo, links, and theme toggle.

Footer

Site footer with links and brand elements.

Articles

Article list layout for blog and case study indexes.

Flip Card

3D flip card with front and back faces.

Form Inputs

Text input field with label, hint, and validation states.

Checkbox

Custom checkbox with label, checked, and disabled states.

Radio

Radio button group for single-selection options.

Toggle

On/off switch for boolean settings.

Textarea

Multi-line text input with auto-resize support.

Select

Styled dropdown for choosing from a list of options.

File Upload

Drag-and-drop file picker with file list display.

Search Input

Search field with icon and clear button.

Toast

Transient notification messages with auto-dismiss.

Spinner

Animated loading indicator in multiple sizes.

Skeleton

Placeholder shimmer for content loading states.

Progress Bar

Linear progress indicator with labeled value.

Tooltip

Contextual label on hover, positioned in four directions.

Popover

Rich overlay panel triggered by a button click.

Drawer

Side panel that slides in from left or right.

Breadcrumb

Hierarchical path trail with separator glyphs.

Pagination

Page-by-page navigation with prev/next controls.

Stepper

Step-by-step progress indicator for multi-stage flows.

Accordion

Collapsible sections for progressive disclosure of content.

Divider

Horizontal or vertical rule for visual separation.

Table

Data table with striped rows and responsive overflow.

Data List

Key-value pairs for structured metadata display.

Avatar

User portrait or initials fallback in multiple sizes.

Tag / Chip

Compact label for categories, filters, and selections.

Callout

Prominent inline block for tips, notes, and warnings.

Empty State

Illustrated placeholder for zero-content views.

Kbd

Keyboard shortcut key display for documentation.

Stat Card

Metric display with value, label, and trend indicator.

Buttons

Buttons trigger actions and events. Peraa provides three button intents for different levels of emphasis: primary, secondary, and tertiary.

HTML
<button class="peraa-btn peraa-btn--primary">
  Primary
</button>

<button class="peraa-btn peraa-btn--secondary">
  Secondary
</button>

<button class="peraa-btn peraa-btn--tertiary">
  Tertiary
</button>
JSX
import { Button } from '@peraa/react';

<Button intent="primary">Primary</Button>
<Button intent="secondary">Secondary</Button>
<Button intent="tertiary">Tertiary</Button>

// With additional props
<Button intent="primary" size="lg">
  Large Button
</Button>
<Button intent="primary" disabled>
  Disabled
</Button>
HTML
<button class="peraa-btn-primary">
  Primary
</button>

<button class="peraa-btn-secondary">
  Secondary
</button>

<button class="peraa-btn-tertiary">
  Tertiary
</button>

<!-- With sizes -->
<button class="peraa-btn-primary peraa-btn-sm">Small</button>
<button class="peraa-btn-primary peraa-btn-lg">Large</button>

Props / Classes

Class Description Required
.peraa-btn Base button styles Yes
.peraa-btn--primary High emphasis, solid background No
.peraa-btn--secondary Medium emphasis, outlined No
.peraa-btn--tertiary Low emphasis, text only No
.peraa-btn--sm Small size variant No
.peraa-btn--lg Large size variant No
.peraa-btn--icon-only Square button for icons No
.peraa-btn--full Full-width button No

Design Tokens

Semantic Token Primitive Value Description
--peraa-interactive-primary-bg --peraa-color-purple-500 #6F1FAC Primary button background
--peraa-interactive-primary-bg-hover --peraa-color-purple-600 #5c1a8f Primary button hover
--peraa-interactive-primary-text --peraa-color-neutral-0 #ffffff Primary button text
--peraa-interactive-secondary-border --peraa-color-purple-500 #6F1FAC Secondary button border
--peraa-interactive-secondary-text --peraa-color-purple-500 #6F1FAC Secondary button text

Micro-animations

All motion in Peraa is driven by a shared set of duration and easing tokens. Every transition and keyframe animation below draws from these primitives — making motion consistent, predictable, and easy to update from a single place.

Timing Foundations


Duration Tokens

TokenValueUsage
--peraa-duration-7575msInstant micro-feedback (icon swap)
--peraa-duration-100100msTooltip appear/dismiss
--peraa-duration-150150msHover color, focus ring, radio dot
--peraa-duration-200200msTab fade-in, toast exit, popover in
--peraa-duration-300300msAccordion expand, progress fill, toast enter, drawer overlay
--peraa-duration-500500msFlip card 3D rotation

Easing Tokens

TokenCurveUsage
--peraa-ease-linearlinearSpinner rotation, progress indeterminate
--peraa-ease-incubic-bezier(0.4,0,1,1)Elements leaving the screen (toast out)
--peraa-ease-outcubic-bezier(0,0,0.2,1)Elements entering (most transitions & animations)
--peraa-ease-in-outcubic-bezier(0.4,0,0.2,1)Symmetric motion — drawer slide, flip card

Hover each row to preview the curve

ease-out
ease-in
ease-in-out
linear

Keyframe Animations


Looping and one-shot animations defined with @keyframes. All respect prefers-reduced-motion.

Spinner

Loop
Keyframeperaa-spin
Duration--peraa-duration-500 (500ms)
Easingease-linear
Iterationinfinite

0° → 360° rotation. Used for all loading indicators while awaiting async operations.

Skeleton Shimmer

Loop
Keyframeperaa-shimmer
Duration1500ms
Easingease-in-out
Iterationinfinite

Gradient sweeps left-to-right on skeleton placeholders, signalling content is loading.

Progress Indeterminate

Loop
Keyframeperaa-progress-indeterminate
Duration1500ms
Easingease-in-out
Iterationinfinite

Bar translates across the full track, used when progress percentage is unknown.

Component Transitions


State-change transitions triggered by user interaction. Hover the demos to preview.

Accordion Expand

Panel content with a smooth height transition.
Propertymax-height
Duration300ms
Easingease-out

Drawer Slide

Panel
Propertytransform: translateX
Duration300ms
Easingease-in-out

Flip Card 3D

Front
Back
Propertytransform: rotateY
Duration500ms
Easingease-in-out

Card Hover Lift

Hover over me
Propertiestransform, box-shadow, border-color
Duration150ms
Easingease-out

Button States

Propertiesbackground-color, box-shadow, transform
Duration150ms
Easingease-out

Form Focus Ring

Propertiesborder-color, box-shadow
Duration150ms
Easingease-out

Radio & Checkbox

Radio dottransform: scale 150ms ease-out
Checkboxbackground-color 150ms ease-out

Image Placeholder Reveal

Overlayopacity 0→1, 200ms ease-out
Image scaletransform: scale 1→1.05, 300ms ease-out
CTA arrowtransform: translateX 200ms ease-out

Accessibility: Reduced Motion


@media (prefers-reduced-motion: reduce)

Every animation and transition in this system is wrapped with a prefers-reduced-motion media query. When the user's OS has reduced motion enabled, all durations collapse to 0.01ms and all looping animations are paused. No extra code is required in consuming components.

CSS
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Design Tokens

All design tokens used in the Peraa design system. Tokens are organized into two layers: primitives (raw values — colors, sizes, timing) and semantic tokens (contextual mappings — always prefer these in components).

Dark mode is the default

:root maps to the dark theme in tokens/_semantic.css. Light mode is opt-in via [data-theme="light"]. A blocking inline script in each page's <head> reads localStorage or prefers-color-scheme and sets data-theme synchronously, eliminating flash of unstyled content (FOUC) on load.

Color Primitives

Token Value Preview Description
--peraa-color-purple-50 #f5e9fc Lightest purple
--peraa-color-purple-100 #e5c7f7 Very light purple
--peraa-color-purple-200 #d3a1f1 Light purple
--peraa-color-purple-300 #bf79eb Medium light purple
--peraa-color-purple-400 #ab51e5 Medium purple
--peraa-color-purple-500 #6F1FAC Primary brand color
--peraa-color-purple-600 #5c1a8f Dark purple
--peraa-color-purple-700 #491572 Darker purple
--peraa-color-purple-800 #361055 Very dark purple
--peraa-color-purple-900 #230b38 Darkest purple

Neutral Colors

Token Value Preview Description
--peraa-color-neutral-0 #ffffff White
--peraa-color-neutral-50 #fafafa Off-white
--peraa-color-neutral-100 #f4f4f5 Light gray
--peraa-color-neutral-200 #e4e4e7 Border gray
--peraa-color-neutral-300 #d4d4d8 Medium light gray
--peraa-color-neutral-400 #a1a1aa Disabled text
--peraa-color-neutral-500 #71717a Tertiary text
--peraa-color-neutral-600 #52525b Secondary text
--peraa-color-neutral-700 #3f3f46 Dark gray
--peraa-color-neutral-800 #27272a Very dark gray
--peraa-color-neutral-900 #18181b Near black
--peraa-color-neutral-950 #09090b Darkest

Teal (Brand Accent)

The secondary brand palette. Used for ambient glows, interactive hover states, card accent borders, and CTAs that need to read as "active" without purple. The 400–600 range is the primary active zone.

TokenValuePreviewUsage
--peraa-color-teal-50#f0fdfaLightest tint — alert backgrounds
--peraa-color-teal-100#ccfbf1Success backgrounds
--peraa-color-teal-200#99f6e4Hover fills (light mode)
--peraa-color-teal-300#5eead4Subtle accents
--peraa-color-teal-400#2dd4bfActive accents in dark mode
--peraa-color-teal-500#14b8a6Brand accent — glow base, card hover borders
--peraa-color-teal-600#0d9488Pressed / active state
--peraa-color-teal-700#0f766eDark variant fills
--peraa-color-teal-800#115e59Deep background tints
--peraa-color-teal-900#134e4aDarkest tint
--peraa-color-teal-950#042f2eNear-black for overlays

Semantic Tokens

Semantic tokens are the layer between raw color values and your UI. Always prefer semantic tokens over primitives — they automatically respond to the active theme (light or dark) so components remain consistent without per-component media queries.

Surface

TokenLight valueDark valueDescription
--peraa-surface-page#fafafa#09090bMain page background. Dark mode carries ambient teal glow as background-image layers.
--peraa-surface-card#ffffff#18181bCard / panel background
--peraa-surface-elevated#ffffff#27272aDropdowns, tooltips, modals — above card level
--peraa-surface-subtle#f4f4f5#3f3f46Muted fills, hover states, tag backgrounds
--peraa-surface-overlayrgba(0,0,0,0.4)rgba(0,0,0,0.65)Modal / drawer scrim overlay

Text

TokenLight valueDark valueDescription
--peraa-text-primary#09090b#fafafaBody text, headings
--peraa-text-secondary#71717a#a1a1aaDescriptions, subheadings
--peraa-text-tertiary#a1a1aa#52525bPlaceholders, hints, disabled labels
--peraa-text-disabled#d4d4d8#3f3f46Fully disabled state text
--peraa-text-brand#6F1FAC#ab51e5Overlines, active nav links, brand callouts
--peraa-text-inverse#fafafa#09090bText on filled/inverted surfaces

Border

TokenLight valueDark valueDescription
--peraa-border-default#e4e4e7#27272aDefault dividers, card borders
--peraa-border-subtle#f4f4f5#18181bVery light separation
--peraa-border-strong#a1a1aa#52525bEmphasis borders
--peraa-border-brand#6F1FAC#ab51e5Brand-colored border
--peraa-border-focus#6F1FAC#ab51e5Keyboard focus ring

Interactive States

TokenLight valueDark valueUsage
--peraa-interactive-primary-bg#6F1FAC#6F1FACPrimary button fill
--peraa-interactive-primary-bg-hover#5c1a8f#ab51e5Primary button hover
--peraa-interactive-primary-text#ffffff#ffffffText on primary button
--peraa-interactive-secondary-bgtransparenttransparentSecondary button fill
--peraa-interactive-secondary-border#6F1FAC#ab51e5Secondary button border
--peraa-interactive-tertiary-text#6F1FAC#ab51e5Tertiary / text-only button

Spacing

Token Value Pixels Description
--peraa-spacing-10.25rem4pxExtra small
--peraa-spacing-1-50.375rem6pxForm gap, tight inline spacing
--peraa-spacing-20.5rem8pxSmall
--peraa-spacing-2-50.625rem10pxInput padding vertical
--peraa-spacing-30.75rem12pxMedium small
--peraa-spacing-41rem16pxBase
--peraa-spacing-51.25rem20pxMedium
--peraa-spacing-61.5rem24pxLarge
--peraa-spacing-82rem32pxExtra large
--peraa-spacing-102.5rem40px2X large
--peraa-spacing-123rem48px3X large
--peraa-spacing-164rem64px4X large
--peraa-spacing-205rem80px5X large
--peraa-spacing-246rem96px6X large

Typography

Token Value Description
--peraa-font-family-sans'Outfit', sans-serifPrimary UI font — all headings and body
--peraa-font-family-mono'JetBrains Mono', monospaceCode blocks, tokens, technical labels
--peraa-font-size-xs0.75rem (12px)Captions, badges, fine print
--peraa-font-size-sm0.875rem (14px)Small body, table cells
--peraa-font-size-base1rem (16px)Default body text
--peraa-font-size-lg1.125rem (18px)Lead / intro copy
--peraa-font-size-xl1.25rem (20px)Section subheadings
--peraa-font-size-2xl1.5rem (24px).peraa-heading-4
--peraa-font-size-3xl1.875rem (30px).peraa-heading-3
--peraa-font-size-4xl2.25rem (36px).peraa-heading-2
--peraa-font-size-5xl3rem (48px).peraa-heading-1
--peraa-font-size-6xl3.75rem (60px)Hero / display headings
--peraa-font-weight-regular400Body text, descriptions
--peraa-font-weight-medium500h3–h6, UI labels, overlines
--peraa-font-weight-semibold600h1–h2 (Direction A editorial style)
--peraa-font-weight-bold700Reserved — use sparingly for max emphasis

Border Radius

Token Value Preview Description
--peraa-radius-none 0 No radius
--peraa-radius-sm 0.25rem (4px) Small
--peraa-radius-md 0.5rem (8px) Medium (default)
--peraa-radius-lg 0.75rem (12px) Large
--peraa-radius-xl 1rem (16px) Extra large
--peraa-radius-2xl 1.5rem (24px) Large cards, modals
--peraa-radius-full 9999px Pill shape

Shadows

Token Preview Description
--peraa-shadow-xs Extra small shadow
--peraa-shadow-sm Small shadow
--peraa-shadow-md Medium shadow
--peraa-shadow-lg Large shadow
--peraa-shadow-xl Extra large shadow
--peraa-shadow-2xl 2X large — modals, overlays

Line Height

TokenValueDescription
--peraa-line-height-tight1.2Large headings (h1, h2) — condensed for display use
--peraa-line-height-snug1.375Subheadings (h3–h4)
--peraa-line-height-normal1.5Body text — default reading rhythm
--peraa-line-height-relaxed1.625Long-form reading, articles
--peraa-line-height-loose2Spacious UI labels, table rows

Letter Spacing

Direction A (Premium Dark Editorial) uses negative tracking on large headings to tighten display type. Overlines use wide tracking to add visual contrast against heading copy.

TokenValueUsed on
--peraa-letter-spacing-tighter-0.05emReserved for extreme display use
--peraa-letter-spacing-tight-0.025em.peraa-heading-2 (Direction A)
--peraa-letter-spacing-normal0Body text, most UI elements
--peraa-letter-spacing-wide0.025emOverlines, small labels
--peraa-letter-spacing-wider0.05emALL CAPS labels
--peraa-letter-spacing-widest0.1emDecorative overlines at small sizes

Border Widths

TokenValueDescription
--peraa-border-width-00No border (use to override)
--peraa-border-width-11pxDefault — all cards, dividers, inputs
--peraa-border-width-22pxActive tab indicator, focus rings
--peraa-border-width-44pxAlert left-border accent, timeline tracks

Animation — Duration

TokenValueUse case
--peraa-duration-7575msInstant micro-interactions (checkbox tick)
--peraa-duration-100100msIcon state changes
--peraa-duration-150150msButton press feedback
--peraa-duration-200200msHover transitions (color, border)
--peraa-duration-300300msPanel reveals, tab content switches
--peraa-duration-500500msPage entrance animations, theme fade

Animation — Easing

TokenValueUse case
--peraa-ease-linearlinearProgress bars, loaders
--peraa-ease-incubic-bezier(0.4, 0, 1, 1)Elements leaving the screen
--peraa-ease-outcubic-bezier(0, 0, 0.2, 1)Elements entering the screen (default)
--peraa-ease-in-outcubic-bezier(0.4, 0, 0.2, 1)State toggles, flip cards, modals

Z-Index

Use only the defined Z layers — never use arbitrary values. Layers establish a clear stacking context that prevents unresolvable overlap bugs.

TokenValueIntended layer
--peraa-z-00Base / no stacking context needed
--peraa-z-1010Sticky elements — table headers, inline toolbars
--peraa-z-2020Dropdowns, floating cards
--peraa-z-3030Sticky site header / navigation bar
--peraa-z-4040Drawers, side panels
--peraa-z-5050Modals, dialogs
--peraa-z-100100Toasts, alerts, top-level notifications

Glass — Semantic Tokens

All values are defined per theme. Apply via the .peraa-glass component class — do not use these tokens directly in component styles unless you are building a glass variant.

TokenDark valueLight value
--peraa-glass-bgneutral-800 solidTranslucent white + teal/purple radial gradients
--peraa-glass-borderneutral-700rgba(255,255,255,0.75)
--peraa-glass-blurnoneblur(20px) saturate(1.4)
--peraa-glass-shadownoneLayered outer shadow + inner top highlight
--peraa-glass-radius--peraa-radius-xl (1rem)

Focus Ring

TokenDarkLight
--peraa-focus-ring-shadow0 0 0 3px rgba(purple-500, 0.2)0 0 0 3px rgba(purple-500, 0.15)

Status — Subtle Backgrounds

Low-opacity status backgrounds used for feedback banners and toast surfaces. Theme-aware.

TokenDescription
--peraa-status-success-bg-subtleMuted green tint — success feedback banners
--peraa-status-warning-bg-subtleMuted amber tint — warning feedback banners
--peraa-status-error-bg-subtleMuted red tint — error feedback banners
--peraa-status-info-bg-subtleMuted blue tint — info feedback banners