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
- The app frame
- The curved signature
- Panels
- Themes and tokens
- Arabic and right to left
- Layout helpers
- Icons and fonts
- JavaScript API
- Installable app
- Store apps
- Build and source
- Browser support
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>| Class | What it does |
|---|---|
mk-topbar--hero | A 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--center | Centres the title, for detail screens. |
mk-topbar--flat | No curve and no colour. Only for full-screen photos and video. |
mk-tabbar--icons | Hides labels on screen; they stay for screen readers. |
mk-tabbar__item--action | A 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.
- Top bar: 70px tall. Its bottom-end corner is rounded 70px, and an inverse curve at the top-start, under the bar, carries the bar colour down into the screen.
- Bottom bar: 70px tall. Its top-start corner is rounded 80px, and an inverse curve at the bottom-end carries the colour up.
- Built with logical CSS properties, so both curves mirror on their own in Arabic.
- Panels, cards, alerts, toasts and the floating button echo the curve on one corner.
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-depth | Look |
|---|---|
lifted (default) | A close contact shadow plus a long soft one; the bars float. |
flat | No shadow. |
soft | One gentle shadow. |
glow | The shadow takes the theme colour. |
ribbon | A solid, darker echo of the curve behind it. |
embossed | Lit 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>mk-drawerslides in from the start side (mk-drawer--endfor the other side). Its header uses.mk-drawer__header, links.mk-drawer__link.mk-sheetslides up from the bottom. Dragging its handle down closes it.mk-dialogopens in the centre, with__icon,__header,__bodyand__footer.- A tap on the dimmed backdrop closes any panel. The page behind does not scroll while one is open.
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-accent | ocean (default), violet, lagoon, forest, sunset, rose, midnight, sand, or your own |
data-mk-theme | light, dark, auto (follows the device) |
data-mk-depth | lifted, flat, soft, glow, ribbon, embossed |
dir and lang | ltr 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
| Group | Tokens |
|---|---|
| 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.
- Icons that point (arrows, chevrons) take
mk-flip-rtlso they turn round. - Amounts, card numbers and codes keep left-to-right order: wrap them in
<bdi>or usemk-amountormk-ltr. - The demo pages show both languages with
data-mk-ar="…"on an element anddata-mk-ar-label="…"for itsaria-label(data-mk-ar-placeholder="…"for a placeholder). This helper is for demos; in your app, serve each language from your own translations.
Layout helpers
| Class | Use |
|---|---|
mk-stack (--xs, --sm, --lg, --xl) | A column with even gaps |
mk-cluster | A wrapping row with gaps |
mk-split | Two things pushed to the two ends of a row |
mk-grid (--3, --4) | Equal columns |
mk-scroller | A row that scrolls sideways with snap points |
mk-visually-hidden | Hidden 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.
| Call | What 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"
}- The build writes
manifest.webmanifestandsw.js(Workbox 7.4.1). Pages load network-first; with no connection, a page that was never visited showspages/offline.html. buyUrlis where every "Get the kit" button goes (home page and showcase). Empty sends them to the getting-started page.- When you publish changes, raise
cacheVersion. Visitors see an "A new version is ready" toast with an Update button. - Any element with
data-mk-installbecomes an Install button. On iOS it opens a sheet with the Add to Home Screen steps (#install-ios, seeindex.html). npm run iconsdraws app icons in youriconColor; or replace the PNGs inpwa/iconswith your own artwork of the same names.- Installing needs HTTPS (localhost works for testing). Turn the service worker off on a page with
<html data-mk-pwa="off">.
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.
index.html?appopens the kit full screen on a desktop for the rest of the visit.- To turn the showcase off, remove the line that loads
docs/assets/to-showcase.jsfromsrc/site/index.htmland rebuild. - The QR code shows the address the page is served from, so it only works from a phone once the kit is online (not on localhost).
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
| Command | What it does |
|---|---|
npm run build | Compiles Sass, assembles dist/, generates the pages, writes the manifest and service worker |
npm run watch:css | Recompiles CSS as you edit |
npm run check | Checks icons, links, alt text, button names |
npm run size | Checks the size budget: CSS 60 KB, core JS 30 KB (gzipped) |
npm run icons, npm run images | Redraw 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.