Skip to content

Components

The everyday parts - buttons, tags, alerts and their many friends, ready to drop in.

Buttons

Three emphasis levels: primary fills, outline draws a line, ghost does neither until you point at it. Every variant dips a pixel when pressed - on touch that press is the only feedback there is.

Button Styles

Ghost, Icon & Block

Ghost

The third emphasis level: primary fills, outline draws a line, ghost does neither until you point at it. For a toolbar or a card's own actions, where five outlined boxes would fight the content under them. Not mw-btn-plain - this keeps the shape, the padding and the hit area, it just does not paint them at rest.

Icon only

mw-btn-icon makes the button square. Without it the horizontal padding is the only thing setting the width, so an icon button ends up wider than it is tall and a row of them lines up on nothing. It always needs an aria-label - there is no text to read out.

Block

mw-btn-block takes the full width - the one action on a sheet, a sign-in form, a narrow sidebar.

Button Sizes

Buttons with Icons

Mini Icon Buttons

Plain Buttons

mw-btn-plain is for everywhere a <button> is the right element and a button is the wrong look - a calendar day, a time in a request row, a modal backdrop. It brings nothing but the focus ring and the pointer; everything else it inherits. Not the same as mw-btn-link, which is a button dressed as a link with a colour and a control height of its own, nor the same as mw-link, which is the link inside a sentence.

Booked for on - both are real buttons, reachable by tab.

Text Links

mw-link is the link inside a sentence, mw-link-muted the quieter one. Same look as mw-btn-link, without the button: mw-btn carries a control height, and in running text that lifts the one line the link sits on above every line around it - glaring in a footer disclaimer, where the text is a size smaller to begin with. So mw-link in a paragraph, mw-btn mw-btn-link where the link really is one of several buttons and has to line up with them. Both work on an <a> and on a <button>.

Built with Astro and written up in the Get Started section - both sit on the line, not above it.

Touch Targets

On a coarse pointer or below 768px the compact controls grow to a size a thumb can actually hit: mw-btn-sm, mw-input-sm and mw-select-sm to 2.5rem, a tab to 2.75rem, a calendar day to 44px. WCAG 2.5.8 puts the floor at 24px, and that is a floor, not a goal. mw-btn-mini keeps its 18px circle - it sits in tag rows and table cells where a bigger one would shift the layout - and grows its hit area to 28px instead, as does a kanban action button on its 32px box. Nothing to switch on; resize this page or open it on a phone.

Button Bars

A framed row of actions. Below 576px it stacks and every button takes the full width. The colour variants style whatever is inside them, so a bar of five buttons needs one class rather than five.

Basic Button Bar

Auto-styled Primary Button Bar

Auto-styled Secondary Button Bar

Outline Button Bar

Navigation Style (Mixed Buttons)

Space Between Layout

Button Bar Alignments

Left Aligned:

Right Aligned:

Center Aligned (also the default):

Button Bar Sizes

Small:

Default:

Large:

Button Bars with Icons

Navigation with Icons:

Action Buttons:

Progress Bars

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.

Basic Progress Bars

Default (75%)

Project Completion 75

Without Info (50%)

Hidden 50

With scale 1-10

User Rating 7

With inline label (55%)

55%

Colored Progress Bars

Primary (60%)

Downloads 60

Secondary (50%)

Uploads 50

Success (100%)

Task Complete 100

Warning (30%)

Disk Space 30

Danger (15%)

Battery 15

Sizable Progress Bars

Small Progress (65%)

Small 65

Default (65%)

Default 65

Large (65%)

Large 65

Skill Levels

React Junior
Java Professional
Python Senior
Go Expert

Without a figure, and round

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()".

30%
72%
88%

Loading Spinners

Three shapes for the same job. Each one needs its shape class: mw-spinner-primary on its own renders nothing at all.

Basic Spinners

Border Spinner

Dots Spinner

Dual Ring Spinner

Spinner Sizes

Small

Default

Large

Spinner Colors

Default

Primary

Secondary

Spinner in Context

Overlay Spinner

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.

Inline

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".

Loading the last 30 days

Avatars

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.

Avatar Sizes

Avatar
Avatar
Avatar
Avatar
Avatar

Avatar Shapes & Colors & Group

Avatar Shapes

Avatar
Avatar

Avatar Colors

Avatar
Avatar
Avatar
Avatar
Avatar
Avatar

Avatar Initials

For users without a picture. The color variants tint the surface here instead of the border.

MW
AB
CD
EF
GH
IJ
KL
MN

Avatar Group

Avatar
Avatar
Avatar
Avatar

Landscape

Avatar
Avatar

Portrait

Avatar
Avatar

Group With a Count

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.

MB
JR
TA

Tags

A label with a soft surface that names something - a topic, a state, a category - and sits in a row of its own kind. For a count or a status marker reach for a badge instead.

Status & Types

Default

Frontend Backend DevOps

Primary

Angular Spring Boot Kotlin

Success

Completed Deployed Stable

Info

v2.4.0 Beta Documentation

Secondary

Docker Kubernetes Git

Warning

Pending Review

Danger

Failed Deprecated

Muted

Draft Archived

Sizes & Formatting

Default Size

Typescript Components filter: active

Large (Categories)

Web Development Design System

With Details

npm install (latest) main (protected)

Interactive Tags

Removable Input (Primary)

React Vue.js

Removable Status (Success)

Online Available

Large Removable

Filter: Architecture

Single Tag

For a single status chip - e.g. in a table cell - use mw-tag. It carries the colour itself, so no mw-tags container is needed.

Variants

Primary Secondary Paid Info Open Cancelled Draft XRechnung

Neutral vs. Muted

mw-tag-neutral is a plain identifier - a customer number, a document type. mw-tag-muted means the label steps back. On an invoice the difference carries real meaning: “XRechnung” is a property, “no e-invoice” is a shortcoming.

Customer 4711 XRechnung No e-invoice

Large

Paid Draft

Filter chips

A mw-tag written as a <button> or a link is a control: focus ring, and a thumb-sized floor on touch. aria-pressed="true" (or mw-active on a link) fills it and adds a tick, so the state never rests on colour alone.

Badges

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.

Counts

Colors

3 8 1 4 99+ 12 0 new

Sizes

7 7 7

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.

Anchored to something

5 2

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.

Status

Dot with a label

Online Degraded Incident Archived

Live

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.

In a row

Deployment #4812 Passed

Deployment #4811 Failed

Keyboard Keys

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.

In prose

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.

Held down

Space shown mid-press - the raised edge drops and the key sits 1px lower, for a hint that demonstrates rather than lists.

As a shortcut list

Save Ctrl+S

Find in page Ctrl+F

Close tab Ctrl+W

Help ?

Ratings

Change the data-rating attribute (0-5) to see how many thumbs-up light up. You can also determine the icon flexibly.

Ratings in different sizes

User Rating (size SM)

Product Rating

Movie Rating (size LG)

Ratings with other icons

Product Rating

Product Rating

Product Rating

Product Rating

Product Rating

Info Badges & Counters

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.

Pill Badges

Default Small Large XL

Color Badges (with Border)

Primary Secondary Info Success Warning Danger

Block

The same frame, carrying a paragraph The base is built for a short figure and centres it in a narrow column. The block variant ranges its text left up to a measure, sets it in body weight and leading, and stays put under the pointer - for a note, a disclaimer, or a short explanation beside the thing it explains.

Counters

5
Projects Completed
24/7
Support
150
Users
3
Alerts

Mini Info Badges (Sizes)

Mini Info Badges (Colors)

Alerts System

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.

System Update

Please note that maintenance is scheduled for tonight at 02:00 AM.

Notification

You have unread messages in your inbox.

Profile Updated

Your settings have been saved successfully.

Storage Low

You are running out of storage space (95% used). Consider upgrading your plan.

Connection Failed

Could not connect to database. Please check your credentials and try again.

Did you know?

You can use keyboard shortcuts to navigate faster. Press ? to see help.

With actions

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.

Invoice deleted

INV-2026-0917 is gone from the list and stays restorable for 30 days.

Toast Stack

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.

Announcement

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.

Default (primary) with highlight pill - over content, showing the 75% surface and the blur

Warning, without a link

Info, with a close button - the script hides the ribbon

Danger

Code

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.

Inline Code

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.

Code Block

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'));

Code Block with Filename

counter.jsx React
// A simple React counter component
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  return (
    ......
  );
}

Terminal

cd my-project touch index.html styles.css script.js Created 3 files mkdir assets ls -la

Syntax Highlighting (Integration)

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.

Option 1: Manual Highlighting

For simple snippets, you can manually wrap parts of your code with <span> tags using the provided .mw-code-* classes:

manual-example.js JavaScript
const greet = (name) => {
  // Simple greeting
  return `Hello, ${name}!`;
};

Option 2: Using a Syntax Highlighting Library

For automatic highlighting of various languages, integrate a dedicated JavaScript library like Prism.js or Highlight.js.

  1. Include the library's CSS and JavaScript files in your project.
  2. Ensure the library is configured to process code within MaverickWave's <pre><code>...</code></pre> structures, often by adding specific language classes (e.g., <code class="language-javascript">).
  3. You might need to slightly adjust the library's theme or MaverickWave's token styles (.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.

Using Code Components

Basic Structure

Here's how to structure the HTML for MaverickWave's code components:

Inline Code
<p>Use the <code>mw-btn</code> class.</p>
Basic Code Block
<div class="mw-code-container">
  <pre><code>your_code_here();
    more_code();</code></pre>
</div>
Code Block with Header
<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>
Terminal Block
<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).

Skeleton Loader

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.

Text Lines

Article Placeholder

Short Paragraph

Avatar & Content

User Card

Comment Row

Image & Shapes

Image Card

Small Banner

Empty State

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.

Basic & Color Variants

Default

Nothing here yet

Start by adding your first item to see it appear here.

Warning

No results found

Try adjusting your search or filter to find what you're looking for.

More Variants

Success

All done!

You've completed all your tasks. Enjoy the rest of the day.

Primary (small)

No items match

We couldn't find anything matching your query.

Danger

Failed to load

Something went wrong while fetching the data.

Inline

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.

Pricing

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.

Price

Plain price

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

With a discount tag

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%

Old price struck through

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%

Both prices on one line

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%

Not a figure

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

Decimals

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%

Period and note

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.

Sizes

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

Centred and plain

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

Offer cards

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.

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.

Most booked

Studio


The one most teams end up on: enough seats for a squad, no limits that get in the way mid-sprint.

  • 10 projects
  • 100 GB storage
  • Priority support
  • Custom domain

Regular price:59 Euro Now: 49 Euro -17%

Per month, billed yearly.

Enterprise


Anything that needs a contract, a review and a name to call.

  • Unlimited projects
  • SSO and audit log
  • Named contact

On request

Priced per seat, yearly term.

Offer card with a head band

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

  • 1 project
  • 5 GB storage

Studio

Regular price:59 Euro Now: 48,90 Euro

  • 10 projects
  • 100 GB storage
  • Priority support

Team

99 Euro / mo

  • Unlimited projects
  • SSO and audit log

Price in the footer

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

Workshop: Reading a waveform


Three hours on what a spectrogram actually shows you, and what it does not. Bring headphones.

Sat, 22 March

Workshop: Mixing for small rooms


A full day on the room you actually work in, rather than the one the manual assumes.

Billing cycle

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.

Starter


One workspace, three seats. The tier a side project stays on for a long time.

  • 3 seats
  • 10 GB storage
  • Email support

Price: Euro / month

Pro


Everything in Starter, plus the things a team needs once more than one person touches the data.

  • Unlimited seats
  • 200 GB storage
  • Audit log and SSO

Price: Euro / month

Marquee

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.

Seals and promises

  • Master craftsman since 1998
  • 4.9 from 312 reviews
  • Certified eco partner
  • Five-year warranty
  • Same-week appointments

Logos, the other way round

  • TypeScript
  • Vue.js
  • Sass
  • React
  • Python
  • Docker
  • Figma
  • Git
index.html HTML
<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>