Starter
One project, one seat. Enough to see whether this is the right tool.
- 1 project
- 5 GB storage
- Community support
19 Euro / month
Cancel any time.
The everyday parts - buttons, tags, alerts and their many friends, ready to drop in.
A bar with an optional label and scale. The fill runs up from zero when it scrolls into view, so the number gets read rather than just seen.
mw-progress-indeterminate on the bar: a segment sweeps the
track while nobody knows how long it takes, a still hatch where motion is
off. Leave aria-valuenow out - that is what makes it
indeterminate for a screen reader.
mw-progress-ring draws --mw-progress-value as an
arc, and a new value animates there. In Angular:
[style.--mw-progress-value.%]="done()".
Three shapes for the same job. Each one needs its shape class:
mw-spinner-primary on its own renders nothing at all.
This card demonstrates a loading overlay with a spinner.
The content remains visible but appears disabled during loading states, providing clear visual feedback to users while data is being processed.
mw-spinner-inline takes the size and colour of the text it sits
in - a busy button, a line still loading. The button keeps its label and says
aria-busy="true".
A picture or a pair of initials, in five sizes. The colour variants sit on the
border, so they still read over a photo; on an initials avatar they fill the
circle as well. Inside a group the overlap follows the avatar size.
mw-avatar-landscape (3:2) and
mw-avatar-portrait (9:16, 2:3 on -lg) are for a
photo that is not square - meant for the two large sizes. Both run larger than
the round step, and a landscape avatar shrinks to fit a narrow column.
For users without a picture. The color variants tint the surface here instead of the border.
mw-avatar-more closes a group with the number of everyone who did
not fit - give it role="img" and an aria-label that
says so in words.
A badge is not a tag. A tag names something and sits in a row of its own kind; a badge carries a count or a status and usually sits on something - the number on a bell, the dot on an avatar. That is why it fills its colour instead of tinting it, and why it comes with an anchor.
Round on one character, a pill from two on - a
min-width the padding grows past, the same rule an OS
badge follows. The digits are tabular, so a counter ticking 9 to 10
does not shift what sits beside it.
mw-badge-anchor on the thing being badged,
mw-badge-float on the badge. It hangs off the corner and
carries a ring in the surface colour, so it reads as a separate object
rather than a blob on the icon.
mw-badge-pulse adds a ring that grows out and fades. The
dot underneath never moves, so a column of them in a table stays
readable. For something actually running - a job, an open connection -
not for decoration.
Built on <details> and <summary> rather
than a div and a click handler. That buys the whole keyboard
contract for free: the trigger is focusable, Enter and Space toggle it, the
open state lives in the DOM where a screen reader can see it, and the menu
still opens with JavaScript switched off. The framework script only adds what
markup cannot express - closing on Esc and on a click elsewhere.
mw-active marks the current choice, for a menu that picks
rather than acts.
mw-dropdown-end
mw-dropdown-up
The menu is absolutely positioned, so an ancestor that hides its
overflow clips it. The framework's own containers lift that clip while a
menu is open; on your own, one line does the same:
:has(.mw-dropdown[open]) { overflow: visible }.
A kbd or mw-dropdown-shortcut at the end of an item
sits right-aligned in the muted ink, and drops out on touch, where there is no
keyboard to use it on.
Anything that tells someone which key to press should look like the key.
<kbd> is styled directly, so a shortcut in prose needs no
class at all; mw-kbd is there for a span when the
markup is not yours to change.
Press Ctrl+Shift+P to open the command palette, Esc to close it and Enter to run the selection.
On a Mac that is ⌘+⇧+P.
Space shown mid-press - the raised edge drops and the key sits 1px lower, for a hint that demonstrates rather than lists.
Change the data-rating attribute (0-5) to see how many thumbs-up
light up. You can also determine the icon flexibly.
A small solid marker for a status, a label or a count, down to a mini form that fits inside a table cell. A tag names something; this one flags it.
A message that belongs to the page it sits on. The colour carries the severity
and the left rule repeats it, so the meaning survives where the tint does not.
The same markup inside a mw-toast-stack becomes a toast.
Please note that maintenance is scheduled for tonight at 02:00 AM.
You have unread messages in your inbox.
Your settings have been saved successfully.
You are running out of storage space (95% used). Consider upgrading your plan.
Could not connect to database. Please check your credentials and try again.
You can use keyboard shortcuts to navigate faster. Press ? to see help.
mw-alert-actions inside the content puts Undo or Retry under the
message. role="alert" is for what needs attention now,
role="status" for a confirmation that can wait its turn.
Alerts become toasts by putting them in a fixed, stacked container. Positions:
mw-toast-stack-top-right (default),
mw-toast-stack-top-center,
mw-toast-stack-bottom-right,
mw-toast-stack-bottom-center. Auto-dismiss stays the
application's job. Give the stack role="status" and
aria-live="polite", so a screen reader reads each new toast.
<div class="mw-toast-stack mw-toast-stack-top-right">
<div class="mw-alert mw-alert-success">
<div class="mw-alert-icon"><i class="fas fa-check-circle"></i></div>
<div class="mw-alert-content">Entry saved.</div>
</div>
</div>
The container is position: fixed, so it cannot be shown inline
here. Its width is capped at min(380px, calc(100vw - 2rem)), it
lets clicks pass through everywhere except on a toast, and the entry
animation respects prefers-reduced-motion.
The ribbon glued under the fixed header - a discount, a launch note, a
maintenance window. Shown here with mw-announcement-static, so
the demos sit in the flow instead of over this page. Without it the ribbon
fixes itself below the header, stays put while the page scrolls beneath it,
and reserves the anchor scroll offset via
--mw-announcement-height. The surface is 75% opaque with a
backdrop blur - visible wherever content sits behind it, like the first demo
below.
Inline code gets a surface of its own so it reads as a quoted thing; blocks get a filename bar and a terminal variant. The syntax colours are a fixed pair of themes rather than brand tokens - code is easier to read in the colours an editor already uses.
Use inline code for variables, function names, or short
code snippets within your text.
For example, use mw-btn class to create a button or
mw-card to create a card component in your layout.
Use code blocks for multi-line code examples:
// A simple greeting function
function greet(name) {
return `Hello, ${name}!`;
}
// Display greeting in console
console.log(greet('Developer'));
// A simple React counter component
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
......
);
}
MaverickWave provides styling for code containers
(.mw-code-block, .mw-code-container) and basic
styles for common code tokens (like keywords, comments, strings) using
classes such as .mw-code-keyword,
.mw-code-comment, etc.
However, MaverickWave does not include a built-in JavaScript library for automatically parsing code and applying these highlighting classes.
For simple snippets, you can manually wrap parts of your code with
<span> tags using the provided
.mw-code-* classes:
const greet = (name) => {
// Simple greeting
return `Hello, ${name}!`;
};
For automatic highlighting of various languages, integrate a dedicated JavaScript library like Prism.js or Highlight.js.
<pre><code>...</code></pre>
structures, often by adding specific language classes (e.g.,
<code class="language-javascript">).
.mw-code-*) for perfect
visual integration, or simply rely entirely on the chosen library's
theme.
Use an external library for reliable, multi-language syntax highlighting.
Here's how to structure the HTML for MaverickWave's code components:
<p>Use the <code>mw-btn</code> class.</p>
<div class="mw-code-container">
<pre><code>your_code_here();
more_code();</code></pre>
</div>
<div class="mw-code-block"> <!-- Note: .mw-code-block wraps header + container -->
<div class="mw-code-block-header">
<span class="mw-code-block-filename">script.js</span>
<span class="mw-code-block-language">JavaScript</span>
</div>
<div class="mw-code-container">
<pre><code>your_code_here();</code></pre>
</div>
</div>
<div class="mw-terminal">
<span class="mw-terminal-line">npm install maverick-wave</span>
<span class="mw-terminal-line mw-terminal-output">+ maverick-wave added</span>
</div>
Remember to integrate a syntax highlighting library if you need automatic language coloring (see the "Syntax Highlighting" section above).
The shape of the content before the content arrives. Match it to what is actually loading - a skeleton in the wrong shape is worse than a spinner, because the page then moves twice.
What a list shows when it holds nothing. An icon, a line saying what is
missing and, ideally, the action that fills it. The parts are
-icon, -title and -desc.
Nothing here yet
Start by adding your first item to see it appear here.
No results found
Try adjusting your search or filter to find what you're looking for.
All done!
You've completed all your tasks. Enjoy the rest of the day.
No items match
We couldn't find anything matching your query.
Failed to load
Something went wrong while fetching the data.
mw-empty-state-inline for a table body or a panel: the icon
beside the text, the action under it, all on the left.
No invoices in September
Invoices you write this month show up here.
Two pieces that are usually needed together: mw-price, the figure
on its own, and mw-offer, the card it is sold from. The price is
one baseline row - what it cost before, what it costs now, the unit, a
discount tag - and everything in it is sized in em off
--mw-price-size, so mw-price-sm and
mw-price-lg move the whole row at once.
A struck-through price is a visual convention that a screen reader does not
pass on - <s> is announced by almost none of them. Where an
old and a new price stand next to each other, name them with
mw-sr-only, as in the examples below. Without that the reader
hears two prices and no idea which one is being charged.
mw-price-amount plus mw-price-currency. The
unit is a word next to the figure, not part of it - it stays at reading
size while the number carries the weight.
129 Euro
Any mw-tag or mw-badge placed directly in the
row is centred against the digits instead of hung off their baseline.
129 Euro -20%
mw-price-original is muted and struck. Muted alone
reads as a footnote; the line is what says this is no longer the price.
It takes its own line above the new price, because inline the row gets
long and the first thing to wrap away is the discount tag.
Regular price:159 Euro Now: 129 Euro -20%
mw-price-inline puts the old price in front of the new one
instead of above it. For a wide card or a table row that has the width
to spare - check it at the narrow end before reaching for it.
Regular price:159 Euro Now: 129 Euro -20%
mw-price-word for "on request" or "free" - still bold, so
it holds the same slot in a row of cards, but set well below the size a
number gets.
On request
Free Trial
mw-price-fraction sits inside the amount, one step down and
on the baseline. Cents raised to the cap height are a discounter's idiom
- a service price that borrows it reads cheaper than it is.
Regular price:159,90 Euro Now: 129,90 Euro -19%
mw-price-period for what the price is per,
mw-price-note for the line under it. The note is a sibling
of the row, not a child - inside it, it would be set at price size.
49 Euro / month
Billed yearly, cancel any time.
mw-price-sm, default and mw-price-lg. Only
--mw-price-size changes - the struck price, the cents and
the unit follow it.
159 Euro 129,90 Euro
159 Euro 129,90 Euro
159 Euro 129,90 Euro
mw-price-center for a centred card,
mw-price-plain for body colour instead of the brand - a
table row where a coloured price would be the loudest thing on screen
for no reason.
129 Euro
159 Euro 129 Euro
mw-offer goes on a mw-card and does one thing the
card cannot: it pushes the price to the bottom of the body, so the prices in a
row line up even though the feature lists have different lengths. Everything
else - shell, body, footer, the mw-card-feature frame around the
plan you want picked - is the card's.
One project, one seat. Enough to see whether this is the right tool.
19 Euro / month
Cancel any time.
Most booked
The one most teams end up on: enough seats for a squad, no limits that get in the way mid-sprint.
Regular price:59 Euro Now: 49 Euro -17%
Per month, billed yearly.
Anything that needs a contract, a review and a name to call.
On request
Priced per seat, yearly term.
mw-offer-head puts name and price in a solid band at the top of
the card - for when the price is the thing being compared and the feature list
is only its justification. It takes its colour from the
mw-card-addon-* class on the card, the same set the card badge
and the ribbon read.
Basic
19 Euro / mo
Studio
Regular price:59 Euro Now: 48,90 Euro
Team
99 Euro / mo
No modifier for this one - a mw-price dropped into a
mw-card-footer lines up with the button next to it on its own,
and mw-price-sm keeps it from outweighing the action. The layout
for a dated event, where the price is a detail rather than the offer.
Fri, 14 March
Three hours on what a spectrogram actually shows you, and what it does not. Bring headphones.
Sat, 22 March
A full day on the room you actually work in, rather than the one the manual assumes.
A subscription sold on three terms, where the longer one is cheaper. The
switch is a mw-segmented above the grid, not one per card - three
cards with three switches are three states the visitor has to keep in their
head. Every card shows the same thing: what a month works out at on the chosen
term, what the month costs without a discount, and what is actually charged.
Comparing per-month figures is the point. A yearly plan priced at "288 Euro" next to a monthly one at "30 Euro" is not a comparison a reader can make in their head, so the headline figure stays per month on all three terms and the amount that leaves the account moves to the line below it.
One workspace, three seats. The tier a side project stays on for a long time.
Without the term discount: Euro Price: Euro / month
Everything in Starter, plus the things a team needs once more than one person touches the data.
Without the term discount: Euro Price: Euro / month
A strip that keeps moving: partner logos, seals, the one line from a review
everybody should read. The items sit in the track twice - the second group
aria-hidden and inert, so a screen reader reads the
list once and the keyboard never lands on the copy. The pointer or the focus
on the strip holds it still - tabindex="0" makes it focusable
when nothing inside it is.
With reduced motion the strip stands: the first group wraps centered and the
copy is gone. --mw-marquee-duration sets the pace,
mw-marquee-reverse the direction, and a logo shows its colours
only under the pointer.
<div class="mw-marquee" role="region" aria-label="Partners" tabindex="0">
<div class="mw-marquee-track">
<ul class="mw-marquee-group">
<li class="mw-marquee-item"><img src="partner.svg" alt="Partner" /></li>
...
</ul>
<!-- The same items again, for the seam -->
<ul class="mw-marquee-group" aria-hidden="true" inert>
<li class="mw-marquee-item"><img src="partner.svg" alt="" /></li>
...
</ul>
</div>
</div>
Two colors. Everything else - bar, ink, borders, tints - is derived from them at runtime.
Every switch is one class on <html> or one custom property - no build, no rebuild. The box at the bottom is what a project would carry, and it reads back: paste someone else's setup in to see it. The link does the same - Copy link hands someone this page exactly as it stands.
Shown in the preview
Surfaces, controls and type - the section at the bottom of this page.
Elsewhere on the page
The frame around it. Scroll to see these.