MaverickWave
A CSS framework in two files. Grid, 70 components, light and dark mode - no build step, no dependencies. The same files run a client homepage and a SaaS app, and a few tokens give each its own look.
Why MaverickWave
Four decisions the framework has already made for you, so a page looks finished on the first build - on a phone as much as on a desk.
Dark mode, built in
One class on body flips every component. Switchable, or
fixed to one theme at build time.
Mobile first
Touch targets grow on a coarse pointer, modals turn into bottom sheets, and nothing scrolls sideways.
One color, whole palette
Set --mw-primary-color and the hover tones, tints and
borders follow via color-mix().
No build step
Two files from the CDN and you are done. The SCSS sources are there when
you want @use level control.
Get started
Installation
Add MaverickWave CSS framework to your project using one of these methods:
Option 1: CDN
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maverick-wave@6.6.0/maverick-wave.min.css">
<script src="https://cdn.jsdelivr.net/npm/maverick-wave@6.6.0/maverick-wave.min.js"></script>
Option 2: Direct Download
Integration Options
Using Pre-compiled CSS (CDN or Download)
This is the simplest method if you don't need SCSS customization. Link
the compiled maverick-wave.min.css and
maverick-wave.min.js files in your HTML head and before
the closing body tag, respectively.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- viewport-fit=cover is what makes env(safe-area-inset-*) resolve on a notched phone -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>My MaverickWave Project</title>
<!-- Framework CSS -->
<link rel="stylesheet" href="path/to/maverick-wave.min.css">
<!-- Your Custom Styles (load AFTER framework) -->
<link rel="stylesheet" href="your-custom-styles.css">
</head>
<body>
<!-- Your content -->
<!-- Framework JS -->
<script src="path/to/maverick-wave.min.js"></script>
</body>
</html>
Customizing via CSS Variables
Even without SCSS, you can customize colors and fonts by overriding the CSS custom properties. Create your own CSS file and load it after the MaverickWave CSS.
/* your-custom-styles.css */
:root {
/* Brand colors */
--mw-primary-color: #0f766e;
--mw-secondary-color: #b45309;
/* Text on every solid colored surface. The one token that cannot be
derived: a light brand color needs a dark label, a dark one a light
label - set it opposite your primary. */
--mw-accent-text-color: #f2fafa;
/* Per color override of that label, for a palette that does not sit on
one side of the lightness scale - a neon primary needs a dark label
while the rest of the palette keeps the light one. Same token for
secondary, success, warning, danger and info. */
--mw-primary-accent-text-color: #0b0f0a;
/* Page backgrounds. Card, footer and border are derived from them, so
these two are usually all you set. */
--mw-dark-page-background: #172127;
--mw-light-page-background: #f2f6f7;
/* Font */
--mw-font-family-base: 'Your Custom Font', sans-serif;
}
Using SCSS Source Files
For full customization, clone the repository and integrate the SCSS
source directly into your project's Sass build (Dart Sass required).
The entry point is src/scss/main.scss.
MaverickWave uses @use / @forward (modern
Sass module syntax). Pass your overrides through
@use ... with (...) - a plain assignment before the
@use has no effect, because the root colors are declared
with !default.
Theme Mode
Set $mw-theme-mode to control theme behavior:
-
'switchable'(default) - follows the OS;.mw-theme-lightor.mw-theme-darkon<html>pins one side 'dark'- compile dark theme only'light'- compile light theme only
Note: The main header and navigation always use the dark appearance regardless of theme mode.
Example - your main SCSS file
// styles.scss
@use 'path/to/maverick-wave/src/scss/main' with (
// Theme mode (optional, default is 'switchable')
$mw-theme-mode: 'switchable',
// Root colors - everything else is derived from them at runtime
$primary-color: #0f766e,
$secondary-color: #b45309,
$accent-text-color: #f2fafa,
$dark-background: #172127,
$light-background: #f2f6f7,
$dark-text-color: #e8eef0,
$light-text-color: #172127,
// How far card and footer sit from the page background. Below 1 steps
// toward black, above 1 toward white - 0.85 means the card sits at 85%
// of the page's lightness.
$card-surface-dark: 0.85,
$card-surface-light: 1.05,
$footer-surface-dark: 0.75,
$footer-surface-light: 0.95
);
// Your additional styles
body {
// custom overrides
}
Importing Only What You Need
Every layer forwards one module per file and no
@extend crosses a file boundary, so components can be
imported individually. Applications usually have no use for the
marketing components (blog-post, gallery,
content-slider, techstack-bucket,
tiles, coming-soon, ratings,
home).
// styles.scss - the configuration has to come first
@use 'maverick-wave/src/scss/abstracts/variables' with (
$primary-color: #0f766e
);
@use 'maverick-wave/src/scss/base'; // required - carries the :root tokens
@use 'maverick-wave/src/scss/layout/grid';
@use 'maverick-wave/src/scss/layout/page-header';
@use 'maverick-wave/src/scss/components/buttons';
@use 'maverick-wave/src/scss/components/cards';
@use 'maverick-wave/src/scss/components/modals';
@use 'maverick-wave/src/scss/components/tables';
@use 'maverick-wave/src/scss/components/tags';
@use 'maverick-wave/src/scss/form-elements';
@use 'maverick-wave/src/scss/utilities';
Without base every component renders without colors - it
carries the :root custom properties. A subset like the
one above compiles to roughly 113 kB raw / 17 kB gzipped, against 281
kB / 40 kB for the full build.
Mixins and Functions
abstracts brings these into your own rules. They exist on
the SCSS path only - the compiled CSS carries none of them.
-
fluid($min, $max, $from: 'xs', $to: 'md')- a clamp between two sizes. Each end is a key of$font-sizesor a plain rem/px length, so a hero that does not sit on the scale still gets one. The rem term is the point: a vw-only clamp ignores the reader's font size. The default range stops atmd, a headline passes$to: 'xl'. -
media-up($bp)/media-down($bp)- the$breakpointsmap as a query. -
touch-context($bp: 'md')- coarse pointer or narrow viewport, the condition every target-size rule in the framework hangs under. -
touch-floor($size: 2.75rem)- that condition plus amin-height. Not for a link inside a sentence: WCAG 2.5.8 exempts those, and amin-heightthere pushes the lines of the paragraph apart. -
hit-area($grow: 6px)- grows the hit area through::afterwithout touching the silhouette, for a control whose size is the design. Neighbours need a gap of at least twice$grow, or two hit areas overlap.
@use 'maverick-wave/src/scss/abstracts' as *;
.hero h1 {
font-size: fluid(1.95rem, 4.2rem, $to: 'xl');
}
.my-close-button {
@include touch-floor;
}
Angular Integration
Download the compiled files and place them in your Angular project
(e.g. src/assets/), then reference them in
angular.json:
// angular.json
"styles": [
"src/assets/maverick-wave.min.css",
"src/styles.scss"
]
No "scripts" entry for the JS file.
maverick-wave.min.js wires everything up once on
DOMContentLoaded and writes straight into the DOM - in a
SPA everything rendered afterwards stays uninitialized, and the class
toggles happen behind change detection. Rebuild the behaviors in
components; the state classes (mw-active,
open, mw-selected) are the whole contract. A
modal is the exception - write it as a
<dialog class="mw-modal"> and the element brings
Escape, the focus trap and the scroll lock with it.
Alternatively, use the CDN link directly in
src/index.html:
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/maverick-wave@6.6.0/maverick-wave.min.css">
For SCSS integration, use the npm package (or a local copy of
src/scss/) from src/styles.scss:
// src/styles.scss
@use 'maverick-wave/src/scss/main' with (
$primary-color: #3f51b5,
$mw-theme-mode: 'switchable'
);
Theme switching
Light mode is one class on <html>. Keep it in a
service so the choice survives navigation:
// theme.service.ts
readonly light = signal(localStorage.getItem('mw-theme') === 'light');
constructor() {
effect(() => {
const light = this.light();
document.documentElement.classList.toggle('mw-theme-light', light);
document.documentElement.classList.toggle('mw-theme-dark', !light);
localStorage.setItem('mw-theme', light ? 'light' : 'dark');
});
}
Reactive Forms
The mw-field wrapper groups label, control, hint and
error. The control turns red by itself from Angular's
ng-invalid ng-touched; the message is yours:
<div class="mw-field">
<label class="mw-field-label mw-required" for="email">Email</label>
<input id="email" type="email" class="mw-input" formControlName="email" />
@if (email.invalid && email.touched) {
<span class="mw-field-error">
<i class="fas fa-exclamation-circle"></i> Please enter a valid email.
</span>
}
</div>
React Integration
Download the compiled CSS file and import it in your entry point, or
use the CDN link in index.html:
// main.jsx / index.js
import './assets/maverick-wave.min.css';
Do not import maverick-wave.min.js in a SPA - it binds
once on DOMContentLoaded and never sees anything rendered
later. Accordion, tabs, modal and theme toggle are class toggles; bind
them with className from state.
Example usage in a React component:
function App() {
return (
<div className="mw-container">
<h1 className="mw-text-primary">Hello, MaverickWave!</h1>
<button className="mw-btn mw-btn-primary">Get Started</button>
</div>
);
}
For SCSS integration with Vite, copy the source and import from your main stylesheet:
/* src/index.css or src/App.scss */
@use './maverick-wave/src/scss/main';
Adding Custom Fonts
To use custom fonts with MaverickWave, load the font files using
@font-face and then override the CSS font variable.
1. Load the Font
/* your-custom-styles.css */
@font-face {
font-family: 'Your Custom Font';
src: url('/path/to/font.woff2') format('woff2'),
url('/path/to/font.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Your Custom Font';
src: url('/path/to/font-bold.woff2') format('woff2'),
url('/path/to/font-bold.woff') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}
2. Apply the Font
CSS Variable Method:
/* your-custom-styles.css - load AFTER maverick-wave.min.css */
:root {
--mw-font-family-base: 'Your Custom Font', system-ui, sans-serif;
--mw-font-family-heading: 'Your Custom Font', system-ui, sans-serif;
}
Documentation
Table of Contents
- Foundations
- Layout
- Section Head
- Grid System
- Bento Grid
- App Shell
- Footer
- Cards
- Panels
- Tabs
- Accordions
- FAQ
- Tiles
- Breadcrumbs
- Modals
- HTML Lists
- Item Lists
- Leader Row
- Stepper
- Parallax
- Components
- Buttons
- Button Bar
- Progress Bars
- Loading Spinners
- Avatars
- Tags
- Badges
- Dropdown
- Keyboard Keys
- Ratings
- Info
- Alerts
- Announcement
- Code
- Skeleton Loader
- Empty State
- Pricing
- Marquee
- Forms
- Data & Display
- Page Header
- Meta Info
- Tables
- Stats & Meters
- Chat
- Kanban
- Calendar
- Lunch Menu
- Contact
- Pagination
- Timelines
- Image Gallery
- Portrait Gallery
- Mosaic
- Photo Feed
- Story Archive
- Content Slider
- Testimonials
- Prose
- Media
- Specials
- History
History
The major releases so far. Everything in between - the minors and the patch releases - sits in the changelog.
v6.0.0
App shell, drawer, bento, chat, stats, before and after, an action bar and a motion switch arrived, form states were hardened and scroll effects finally run in Firefox. The browser floor moved up to Chrome 123, Firefox 140 and Safari 17.5, and some legacy markup was dropped along the way. A stat group keeps its row even when it is the card itself.
v5.0.0
Everything the framework ships moved into a cascade layer, so vendor CSS loaded next to it stops winning selector fights by accident. The source got a full pass while it was open anyway.
v4.0.0
The calendar arrived in three shapes - month, week and compact - and pagination was reframed to sit next to it without looking borrowed.
v3.0.0
Claude Code joined the workflow: more components, more form elements, and a pile of small fixes it spotted along the way.
v2.0.0
The grid was rebuilt from scratch, with modals, pagination, item lists and the first real form layer put on top of it.
v1.0.0
Let's say initial release - grid, header and a first handful of components, published as they were.
Preview
One page-shaped example, so the variant switches in the palette panel have something to act on that is not a component gallery. Open the panel and the page lands here.
Quarterly report
Numbers for Q3 are in. The full breakdown sits in the sheet, the short version is below.
Finance signs off on Friday, the rest is final.
Revenue
EUR 4.2m, up 12 percent
Carried by the two enterprise deals that closed in September.
Churn
1.8 percent, flat
Both accounts that left cited the same missing integration.
Headcount
41 people, four hires
Two of them on the platform team. One open role left this quarter.