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.
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
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.
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.
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.
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.
- Tile48 × 48, corner radius 10 (20.8%). Coral
#ff7e5fto peach#feb47b, left to right. - Glyph areaShapes sit inside an 86% box (dashed), leaving an even margin.
- Back layerSupporting shapes in white at 50%.
- Front layerOne or two hero shapes in solid white, overlapping the back layer.
- KnockoutsSmall details (a dot, a code bracket) cut out in the tile gradient.
apple-touch-icon.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".
| Size | Where | Sample |
|---|---|---|
18px | Inside buttons and chips | |
22px | Icon buttons, theme toggle | |
24px | Default, 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.
| Token | Use for |
|---|---|
--shape-xs / --shape-s | Inline code, text fields, small tags |
--shape-m | Swatches, tables, code blocks, card icons |
--shape-l | Large buttons, panels, media |
--shape-xl | Cards |
--shape-full | Buttons, 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.
| Screen | Width | --margin | --gap |
|---|---|---|---|
| Compact (phone) | under 600px | 16px | 16px |
| Medium (tablet) | 600–839px | 24px | 20px |
| Expanded (desktop) | 840px and up | 32px | 20px |
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.
| Token | Value | Use for |
|---|---|---|
--dur-s | 150ms | Hover, state layers, color changes |
--dur-m | 300ms | Theme fade, panels, small reveals |
--dur-l | 500ms | Page-level moves, large reveals |
--ease-emph | cubic-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
Icon button & theme toggle
aria-label.Chips
Cards
Web app
Garage Log
Maintenance history and upcoming work, stored as JSON and published on GitHub Pages.
Tool
RSS Reader
Read and unread, starring, nothing else.
.card-icon), overline (.supporting), title, description, then chips or actions pinned to the bottom.<a class="card">.Text fields
<label> above every field. Placeholders are examples, not labels..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;
}
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 havearia-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>