raysan-ui

Design system · v0.1.1

raysan-ui guidelines

How every Raysan Designs site and app looks, reads and behaves. Built on Material Design 3, tuned to the raysandesigns.com look: calm surfaces, outlined cards, a coral-to-peach brand bar and a designed dark mode.

01

Principles

Four rules that settle most design decisions before they come up.

Calm over busy

Flat surfaces, thin outlines, slow subtle motion. Color is saved for meaning: actions, status, the brand bar.

Tokens, not values

Every color, size and radius comes from a token. If a value isn't a token, it's either a new token or a mistake.

Dark is designed

Dark mode has its own palette, not an inversion. Use tokens and it works for free; check it before shipping.

Accessible by default

Readable contrast, visible focus, keyboard reachable, reduced motion respected. Not a later pass.

02

Brand

Four brand elements carry the identity. Use them sparingly so they stay special.

Raysan

Brand mark

Gradient tile with a surface-colored notch. Pair with the project name in title-l. Use .brand + .brand-mark.

Brand bar

Static 4px coral-to-peach line. Use it where a page has no scrolling header, like a single-screen tool or an email. Token: --rd-brand-bar.

Scroll progress

The hero gradient along the bottom of the top app bar, growing left to right as you read, with the colors slowly drifting. The default header for every page; scroll this one to see it. Add .scroll-progress inside the header and load scroll-progress.js.

Hero gradient

Coral, peach, sky and teal, stretched 4× wider than the box and drifting over 15 seconds, so you only see one part at a time. Hero bands, case study covers and logo tiles; never behind body text. Use .hero-gradient, not the raw --rd-hero-grad token.

03

Color

Material Design 3 color roles. Each role comes as a pair: a fill and an "on" color for text and icons placed on it. Ratios are calculated live for the current theme; switch themes to check dark.

Accent roles

Containers

Surfaces

Higher containers sit closer to the reader. Page background is surface; quiet panels use surface-container-low or surface-container.

Text & lines

Brand accents

Raw brand colors, prefixed --rd-. Use them for illustration, charts and brand moments. For UI, reach for a role first.

Using color

Container + on-container
DoPair every fill with its matching "on" role.
Mixed roles
Don'tMix roles from different families. Contrast breaks, especially in dark.
ShippedDraft
DoLet one accent carry meaning; keep the rest neutral.
HotNewCool
Don'tUse raw brand colors as UI fills. Busy, and white text fails contrast.

04

Typography

Two families. Sizes follow a classic scale (12 · 14 · 16 · 18 · 21 · 24 · 36 · 48 · 72) with line heights on an 8px rhythm.

Aa Sofia Sans

The voice: display, headlines, titles, labels, buttons. Token: --md-font

Designed by Botio Nikoltchev and Ani Petrova of the Bulgarian studio Lettersoup (2019). It draws on early-twentieth-century technical sans serifs: confident letterforms, strong vertical emphasis and tense curves, on narrow proportions with a generous x-height. Built to work from big display sizes down to small text, on screen and in print.

Variable weight + italic4 widthsLatin · Greek · CyrillicSIL OFL
Aa Google Sans Text

The reading: body copy and long text. Token: --md-font-body

Descends from Product Sans, introduced with Google's new logo in 2015 to keep the brand consistent at small sizes. Google Sans (2017) adapted it for Material Design with a taller x-height and wider letterforms; Google Sans Text (2020) is tuned for small optical sizes. Google-only for years, it was open-sourced in December 2025.

Text optical sizeGeometric rootsSIL OFL

Use the role, not the size: font: var(--title-m) in CSS or class="title-m" in HTML. Display sizes step down automatically on phones.

05

Iconography

Two tiers. Brand icons mark the things that matter most: callout sections, app icons and favicons. UI icons do the everyday work: buttons, lists, navigation. If everything gets a brand icon, nothing stands out.

Brand icons

Mid to high importance. A coral-to-peach tile with flat white shapes in two tones, drawn as SVG.

Icons & brand
Product & UX
Build & tools
_template.svg
  1. Tile48 × 48, corner radius 10 (20.8%). Coral #ff7e5f to peach #feb47b, left to right.
  2. Glyph areaShapes sit inside an 86% box (dashed), leaving an even margin.
  3. Back layerSupporting shapes in white at 50%.
  4. Front layerOne or two hero shapes in solid white, overlapping the back layer.
  5. KnockoutsSmall details (a dot, a code bracket) cut out in the tile gradient.
Callout sectionsFeature and service cards, section intros. Use the 48px tile above the card title.
App iconsEach app gets its own drawn tile. Export 192 and 512 PNGs for the web manifest and 180 for apple-touch-icon.
FaviconsSame tile as an SVG favicon. At 16–32px, drop the back layer and keep only the front shape so it reads.
Not forButtons, lists, navigation or anything repeated many times on a screen. Those are UI icons.
DoBuild from simple geometric shapes, two tones of white, soft corners.
Don'tUse outlines, extra colors or detailed illustration. It turns muddy at small sizes.

Quick brand tile

No custom drawing yet? .brand-icon-ms puts a filled Material Symbol on the tile. Good for prototypes; draw a proper two-tone SVG before launch.

<!-- Drawn icon (preferred) -->
<img class="brand-icon" src="icons/brand/product-ux.svg" alt="" width="48" height="48">

<!-- Quick tile -->
<span class="brand-icon brand-icon-ms" aria-hidden="true"><span class="ms">timer</span></span>

<!-- Sizes: .sm 32 · default 48 · .lg 64 · .xl 96 -->

UI icons

Everyday icons. Material Symbols Rounded, outlined by default. Write the icon name inside <span class="ms">; add .fill for the selected or active state. Decorative icons get aria-hidden="true".

home
home .fill
search
add
star
star .fill
settings
light_mode
dark_mode
arrow_forward
SizeWhereSample
18pxInside buttons and chips
22pxIcon buttons, theme toggle
24pxDefault, lists, card icons

06

Shape & elevation

Soft and friendly. Cards use extra-large corners; anything you press is fully rounded. The system is flat: no drop shadows, outlines do the structural work.

TokenUse for
--shape-xs / --shape-sInline code, text fields, small tags
--shape-mSwatches, tables, code blocks, card icons
--shape-lLarge buttons, panels, media
--shape-xlCards
--shape-fullButtons, chips, icon buttons, pills

07

Spacing & layout

Spacing moves in 4px steps, mostly 8, 12, 16, 24, 32 and 48. Page margins and grid gaps grow with the screen.

ScreenWidth--margin--gap
Compact (phone)under 600px16px16px
Medium (tablet)600–839px24px20px
Expanded (desktop)840px and up32px20px

Content sits in .wrap (max 1120px, centered). Use .stack for vertical rhythm and .row for inline groups.

08

Motion

Motion explains a change; it never decorates. Short for small things, longer for big moves, always the emphasized easing. Everything stops when the visitor asks for reduced motion.

TokenValueUse for
--dur-s150msHover, state layers, color changes
--dur-m300msTheme fade, panels, small reveals
--dur-l500msPage-level moves, large reveals
--ease-emphcubic-bezier(.2, 0, 0, 1)Everything: fast start, gentle landing

09

Components

The building blocks. Each one is a class (or one tag) you add to plain HTML.

Buttons

Large
Filled · .btn-filledThe one main action on a screen. Save, Submit, Get started.
Tonal · .btn-tonalImportant but secondary: Open, Add another.
Outlined · .btn-outlinedAlternatives next to a filled button: Cancel, Back.
Text · .btn-textLow-emphasis actions in cards, dialogs and toolbars.

Icon button & theme toggle

.icon-btnToolbar actions with a well-known icon. Always add an aria-label.
<theme-toggle>The icon-only sun/moon switch. One tag, placed in the top app bar or footer. Remembers the choice per device.

Chips

Shipped GitHub Pages HTML CSS
.chipBrand green. Status and the one tag that matters.
.chip-neutralQuiet lists: tools, categories, metadata.

Cards

AnatomyIcon (.card-icon), overline (.supporting), title, description, then chips or actions pinned to the bottom.
.cardOutlined, the default. Make the whole card a link with <a class="card">.
.card-filledQuieter surface for secondary content or cards with buttons inside.

Text fields

As it should print on the shirt
Pick a size to continue
Always labelA visible <label> above every field. Placeholders are examples, not labels.
ErrorsAdd .error to the field and say how to fix it in the .hint.

10

Theming a project

A project can wear its own brand (Porsche red for Garage Log, Anycubic blue for the resin matrix) without forking the system. Load a small theme file after raysan-ui and override only the tokens that change.

/* garage-log/theme.css */
:root {
  --md-primary: #8c0000;
  --md-primary-container: #f7dcdc;
  --md-on-primary-container: #5c0000;
  --rd-brand-bar: linear-gradient(90deg,#8c0000,#d5001c);
}
:root[data-theme="dark"] {
  --md-primary: #ffb4a9;
  --md-primary-container: #5c1a12;
  --md-on-primary-container: #ffdad5;
}
OverrideColor roles, fonts, the brand bar, corner radii.
KeepComponent classes, spacing, motion and the theme toggle. They adapt to whatever tokens you set.
PromoteIf two projects need the same new piece, it moves into raysan-ui.

11

Accessibility

Check these before every launch. Most are already handled if you stick to tokens and components.

  • Text meets 4.5:1 contrast (3:1 for 24px+ or bold 18px+). The color badges above show it live.
  • Everything works with a keyboard and the focus ring is visible.
  • Icon-only buttons have an aria-label; decorative icons have aria-hidden="true".
  • Every form field has a visible label and errors explain how to fix them.
  • Images have alt text (empty alt="" if decorative).
  • Page works at phone width with no sideways scrolling.
  • Checked in both light and dark.
  • Animations stop under reduced motion (built into base.css).

12

Get started

Copy template/index.html into a new repo, or paste this into any page's <head>. Pinning to @0.1 picks up fixes but never breaking changes.

<script>try{if(localStorage.getItem("theme")==="dark")document.documentElement.setAttribute("data-theme","dark")}catch(e){}</script>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Google+Sans+Text:wght@400;500;700&family=Sofia+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/raysantos/raysan-ui@0.1/css/tokens.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/raysantos/raysan-ui@0.1/css/base.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/raysantos/raysan-ui@0.1/css/components.css">
<script src="https://cdn.jsdelivr.net/gh/raysantos/raysan-ui@0.1/js/theme-toggle.js" defer></script>
<script src="https://cdn.jsdelivr.net/gh/raysantos/raysan-ui@0.1/js/scroll-progress.js" defer></script>