Skip to content
v6.6.0

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.

More

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-light or .mw-theme-dark on <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-sizes or 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 at md, a headline passes $to: 'xl'.
  • media-up($bp) / media-down($bp) - the $breakpoints map 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 a min-height. Not for a link inside a sentence: WCAG 2.5.8 exempts those, and a min-height there pushes the lines of the paragraph apart.
  • hit-area($grow: 6px) - grows the hit area through ::after without 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;
}

History

The major releases so far. Everything in between - the minors and the patch releases - sits in the changelog.

2026-09-29

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.

2026-09-11

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.

2026-08-19

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.

2026-04-04

v3.0.0

Claude Code joined the workflow: more components, more form elements, and a pile of small fixes it spotted along the way.

2025-06-09

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.

2025-04-30

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.

Draft
Two figures are still provisional

Finance signs off on Friday, the rest is final.

Best

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.

Syncing with the finance sheet The two provisional figures update when it finishes.
Q3 finance internal