Curves Mobile App Kit

Structure

How a Curves Mobile App Kit page is put together. Read this first; after it, every component and page is copy, paste and change the words.

Page setup

Every page loads one stylesheet and two scripts, all minified. Keep mk-theme.min.js in the head without defer, so the saved theme, mode and language apply before the first paint. The readable files (mk.css, mk-theme.js, mk.js) sit beside them for reading and debugging.

When you host the kit, turn on gzip or Brotli compression and long cache lifetimes for dist/ and images; it is the biggest speed win left, and Lighthouse checks for it.

<!doctype html>
<html lang="en" dir="ltr" data-mk-accent="ocean" data-mk-theme="light">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
  <meta name="theme-color" content="#0059b3">
  <link rel="manifest" href="manifest.webmanifest">
  <link rel="stylesheet" href="dist/css/mk.min.css">
  <script src="dist/js/mk-theme.min.js"></script>
  <script type="module" src="dist/js/mk.min.js"></script>
</head>

viewport-fit=cover lets the curved bars reach the screen edges; the kit pads them with the device's safe areas. Zoom stays allowed, as accessibility requires.

The app frame

A screen is a column of three parts: the curved top bar, the scrolling screen, and the curved bottom bar. Both bars stick to their edge while the screen scrolls.

<div class="mk-app">
  <header class="mk-topbar mk-topbar--curve">
    <button class="mk-topbar__action" type="button" aria-label="Open menu">…icon…</button>
    <h1 class="mk-topbar__title">Home</h1>
    <button class="mk-topbar__action" type="button" aria-label="Notifications">…icon…</button>
  </header>

  <main class="mk-screen">
    <!-- your content -->
  </main>

  <nav class="mk-tabbar mk-tabbar--curve" aria-label="Main">
    <a class="mk-tabbar__item" href="home.html" aria-current="page">
      <span class="mk-tabbar__icon">…icon…</span>
      <span class="mk-tabbar__label">Home</span>
    </a>
    <!-- 3 to 5 items -->
  </nav>
</div>
ClassWhat it does
mk-topbar--heroA tall header for home and splash screens. Put a .mk-topbar__row (actions and title) and a .mk-topbar__body (big content) inside.
mk-topbar--centerCentres the title, for detail screens.
mk-topbar--flatNo curve and no colour. Only for full-screen photos and video.
mk-tabbar--iconsHides labels on screen; they stay for screen readers.
mk-tabbar__item--actionA raised round main action in the middle of the bar.

The curved signature

The curves are the kit's identity. Keep mk-topbar--curve and mk-tabbar--curve on every screen.

Sizes come from three tokens: --mk-bar-height (4.375rem), --mk-curve (4.375rem) and --mk-curve-bottom (5rem). The bars' depth is one setting on <html>:

data-mk-depthLook
lifted (default)A close contact shadow plus a long soft one; the bars float.
flatNo shadow.
softOne gentle shadow.
glowThe shadow takes the theme colour.
ribbonA solid, darker echo of the curve behind it.
embossedLit from above: lighter top edge and a highlight line.

Panels: side menu, bottom sheet, dialog

All three are native <dialog> elements. The browser traps focus inside, closes them on Escape and hides the page behind from screen readers. Place them after .mk-app.

<button type="button" data-mk-open="share" aria-controls="share" aria-expanded="false">Share</button>

<dialog class="mk-sheet" id="share" aria-labelledby="share-title">
  <button class="mk-sheet__handle" type="button" data-mk-close aria-label="Close"></button>
  <div class="mk-sheet__header"><h2 class="mk-sheet__title" id="share-title">Share</h2></div>
  <div class="mk-sheet__body">…</div>
  <div class="mk-sheet__footer"><button class="mk-btn" type="button" data-mk-close>Done</button></div>
</dialog>

Themes and tokens

Every colour, size, corner, shadow and timing is a CSS custom property starting with --mk-. Change the look by changing tokens, never by overriding component classes.

Setting on <html>Values
data-mk-accentocean (default), violet, lagoon, forest, sunset, rose, midnight, sand, or your own
data-mk-themelight, dark, auto (follows the device)
data-mk-depthlifted, flat, soft, glow, ribbon, embossed
dir and langltr and en, or rtl and ar

Every accent theme was checked: text on the bar reaches at least 5.5:1 and accent text at least 4.5:1 on page, card and sunken surfaces, in light and dark. Make your own with the theme builder, which checks the same rules and exports the CSS.

Main tokens

GroupTokens
Theme--mk-bar-bg, --mk-on-bar, --mk-accent, --mk-accent-surface, --mk-bar-gradient-end
Surfaces--mk-surface-page, -card, -sunken, -overlay
Text--mk-text, --mk-text-muted, --mk-border, --mk-border-strong
Status--mk-success, -warning, -danger, -info and their -surface tints
Type--mk-font-ui, --mk-font-display, --mk-text-xs to --mk-text-4xl, --mk-weight-*
Space--mk-space-1 (4px) to --mk-space-8 (64px), --mk-gutter
Shape--mk-radius-sm, -md, -lg, -pill, --mk-tap (44px touch target)
Depth and motion--mk-shadow-sm, -md, -lg, --mk-duration*, --mk-ease

The kit's CSS sits in cascade layers (mk-tokens, mk-vendor, mk-base, mk-components, mk-utilities). Your own CSS, written outside any layer, always wins without !important.

Arabic and right to left

Set dir="rtl" and lang="ar" on <html>; the layout, curves, drawers, switches and carousels mirror by themselves. Readex Pro and Lalezar both cover Arabic.

Layout helpers

ClassUse
mk-stack (--xs, --sm, --lg, --xl)A column with even gaps
mk-clusterA wrapping row with gaps
mk-splitTwo things pushed to the two ends of a row
mk-grid (--3, --4)Equal columns
mk-scrollerA row that scrolls sideways with snap points
mk-visually-hiddenHidden on screen, read by screen readers

Bootstrap 5.3.8's grid, containers and utilities are also included, inside the mk-vendor layer.

Icons and fonts

Icons come from Font Awesome Free 7 as one small SVG sprite. List the icons you need in src/icons.txt and run npm run build.

<svg class="mk-icon" aria-hidden="true"><use href="dist/icons/mk-icons.svg#house"></use></svg>

Fonts are Readex Pro (interface and text, weights 160 to 700) and Lalezar (display). Both are under the SIL Open Font License and self-hosted in dist/fonts. To change them, edit src/scss/_fonts.scss and the --mk-font-* tokens.

JavaScript API

mk.js is one ES module with no framework. It listens at the document level, so buttons added later work without setup.

CallWhat it does
MK.set("accent", "ocean")Changes accent, theme, depth or dir and remembers it
MK.get("theme")Reads the current value
MK.open("id"), MK.close("id")Opens or closes a panel
MK.toast("Saved", { tone, position, duration, action })Shows a toast or a snackbar with an action
MK.init(element)Wires up tabs, carousels and photo viewers in content added later
MK.load("splide.min.js")Loads a file from dist/vendor once
document.addEventListener("mk:change", …)Runs when the accent, theme, depth or language changes

mk-qr.js (separate, about 3 KB) draws QR codes with no outside service: <div class="mk-qr" data-mk-qr="https://example.com"></div>, or MKQR.svg(text). An empty data-mk-qr uses the page address. Codes are always dark on white so they scan in dark mode too.

Markup attributes: data-mk-open, data-mk-close, data-mk-set, data-mk-toggle, data-mk-toast, data-mk-carousel, data-mk-filter, data-mk-reveal, data-mk-copy, data-mk-install, data-mk-single.

Installable app (PWA)

The kit installs to the home screen and works offline after the first visit. Set everything in mk.config.json, then run npm run build:

{
  "name": "Curves Mobile App Kit",
  "shortName": "Curves",
  "startUrl": "index.html",
  "themeColor": "#0059b3",
  "backgroundColor": "#f5f6fa",
  "iconColor": "#0059b3",
  "cacheVersion": "1.4.0",
  "buyUrl": "https://your-store.example/buy"
}

Desktop showcase

On a desktop screen, index.html opens showcase.html: the live kit in a phone frame, with buttons that change its colour, curve depth, mode and language, and a QR code that opens the kit on a phone. Phones and tablets go straight to the kit.

Store apps (Capacitor)

native/ wraps the kit with Capacitor 8.6.0 for Google Play and the App Store. See native/README.md: npm install, npm run add:android, npm run open:android.

Build and source

CommandWhat it does
npm run buildCompiles Sass, assembles dist/, generates the pages, writes the manifest and service worker
npm run watch:cssRecompiles CSS as you edit
npm run checkChecks icons, links, alt text, button names
npm run sizeChecks the size budget: CSS 60 KB, core JS 30 KB (gzipped)
npm run icons, npm run imagesRedraw the app icons and the demo pictures

Pages are generated from short fragments in src/site/ into plain HTML at the same path. Edit the fragment and rebuild, or edit the finished HTML directly if you don't need the generator. Tokens in fragments: {{r}} (path to the kit root), {{i:icon-name}}, {{tabbar:wallet:home}}.

The stack, each at its latest version on 7 November 2026: Bootstrap 5.3.8, Dart Sass 1.108.1, Splide 4.1.4, Swup 4.10.0 (optional), Chart.js 4.6.1, GLightbox 3.3.1, Font Awesome Free 7.3.1, Workbox 7.4.1, Vite 8.3.6, Capacitor 8.6.0. Licences are collected in LICENSES.

Browser support

The last two versions of Safari on iOS, Chrome on Android, and Chrome, Edge, Firefox and Safari on desktop. The kit uses CSS cascade layers, color-mix(), :has(), logical properties and @starting-style for panel animations; where a browser lacks @starting-style, panels open without the slide.