MobTranslate styleguide

Shape

Choose corners, silhouettes and transitions deliberately while preserving content, semantics and focus.

Shape anatomy

Treat boundary, corners, interior space and content as one composition. Use related type/shape characteristics to give a region a coherent voice, while retaining the document hierarchy and clear action labels. A rounded outline does not establish a reading group, permission or review authority.

Reserve abstract shapes for a deliberate emphasis or decorative moment. Contrasting square and round treatments can introduce visual tension, but making every surface different weakens the hierarchy. An authored image crop or avatar mask can be more expressive than an information-dense definition panel. Do not borrow cultural motifs as generic decoration; permitted contributed artwork remains attributed content.

Keep meaning in text, semantics and explicit state. A wave can decorate progress or a container; it is not intrinsically a progress symbol. Never infer a user’s mood or identity from text and automatically choose a shape. Layered shapes, elevation and restrained movement can suggest depth on a flat screen; that visual illusion does not supply a 3D coordinate system or improve source certainty.

Radius specifications

The table reads the generated shared CSS catalogue. Several values use rem, so they follow the root font size; displaying a fixed pixel conversion would hide that behavior. The full token is a large radius request, not a promise that the browser renders a 9999px arc. CSS scales radii down when adjacent corners do not fit the box. The global --radius alias follows its referenced token.

TokenCommitted CSS value
--radiusvar(--radius-lg)
--radius-2xl2rem
--radius-full9999px
--radius-lg1rem
--radius-md0.5rem
--radius-none0px
--radius-sm0.25rem
--radius-xl1.5rem

Reference scale and platform boundaries

The captured Material corner-scale chapter has ten levels and separate individual-corner values. Its dp distances are platform reference data, not CSS conversions or replacement MobTranslate tokens. Existing local components retain their authored radius recipes. The reference’s expanded collection contains 35 shapes; its morph APIs are platform-specific; the captured morph chapter reports no web API. Our compiled lab demonstrates CSS corner interpolation rather than claiming to port that collection.

Reference styleReference value
None0dp
Extra small4dp
Small8dp
Medium12dp
Large16dp
Large increased20dp
Extra large28dp
Extra large increased32dp
Extra extra large48dp
FullFully rounded, normalized to the box

Symmetric, asymmetric and inner corners

Use equal corners when a family should read as one repeated treatment. Individual corners can connect grouped items: preserve outer silhouette while changing the joining edges. Define which corner belongs to the block/inline start or end, and test the same grouping in both directions. Logical corners follow the container direction; source spelling and media orientation need their own policy.

An inner corner is a relationship to the surrounding component group, not simply a smaller global token. Split controls and grouped menu surfaces need adjacency, focus and separators designed together. A corner change must not erase the independent name, target or keyboard behavior of each action. The lab exposes the four actual logical CSS properties and a local direction switch.

Applying shape

Changing a shared radius token affects every recipe that refers to it. Remapping one component recipe changes that family without redefining the entire scale. A local specimen override changes only that specimen. Record the chosen scope, affected states and regression checks before adopting a new product treatment. Use the current component implementation as the source of truth rather than assuming every component has a dedicated radius token.

A cut corner replaces an arc with a straight diagonal. At the same corner magnitude it removes more area than a circular round: the model below shows that difference explicitly. Its removed-area calculation is not a guarantee of usable text space. Preserve extra padding, labels and focus on an outer wrapper; apply an SVG or CSS mask only to a separate decorative/media child. Do not clip an interactive ancestor to create a silhouette.

Optical roundness and nested geometry

For aligned circular corners on nested rectangles, a useful starting relation is inner radius = outer radius − inset, clamped at zero. If the inset exceeds the radius, the inner corner is square. The model uses stroke centerlines and bounded radii that fit both rectangles; it does not equate SVG coordinates with CSS border-box geometry or device dp.

Inspect actual borders, padding, oversize/full normalization and unequal logical corners separately. A single scalar relation does not solve cut corners, elliptical radii or a different mask. Match the inner edge to the outer geometry, then inspect the visual result at the real rendered scale. Do not reuse the same radius at every nested level merely because the outer token is familiar.

Shape explorer

Choose base/alternate tokens, edit individual logical corners, change direction and toggle the presentation state. Draft base corners survive an alternate treatment. The declaration output follows the actual applied state. The separate SVG model and equivalent text expose the nesting relation and cut/round removed areas.

Base corners applied. This is presentation state, not source review or an external action.

Source details

The container changes its corners. Content remains in the DOM; the disclosure retains its native semantics.

View source example

Original English interface specimen. A real entry would carry its own source and review records.

Changes use the current duration/easing tokens. Reduced motion removes the transition. Radius does not define a hit area, focus behavior or meaning; large corners can require more padding than this deliberately adjustable specimen.

Current corner CSS
border-start-start-radius: var(--radius-lg);
border-start-end-radius: var(--radius-lg);
border-end-start-radius: var(--radius-lg);
border-end-end-radius: var(--radius-lg);
Optical nesting and corner-area modelRounded nesting and cut-corner comparisonSolid outer and inner rounded rectangles, dashed outer cut corners. Outer magnitude 48, inset 14, inner radius 34, in model units. Stroke centerlines are compared, not CSS border boxes.

Inner radius = max(0, 48 − 14) = 34. The circular corners share their center when the inner radius is positive.

At the same corner magnitude 48, four cut corners remove 4608.00 square model units; four circular rounded corners remove 1977.77. Both start from a square with non-overlapping corner regions. This compares removed area, not usable text padding.

The drawing scales to available width. These are model coordinates, not device dp or guaranteed rendered CSS pixels. Browser normalization of oversized/full radii and real border thickness need separate inspection.

Morphing, interruption and semantic state

Use shape changes to clarify a selected state, a pending action or an authored environmental response, with equivalent state text. Choose that association deliberately: an expressive outline alone cannot tell a reader whether data was saved. Progress and loading still need their existing status/progress semantics; shape does not become a new source of truth.

The lab changes a real boolean state immediately and lets CSS interpolate compatible border-radius properties. Similar corner changes retarget from their current displayed values when interrupted; no action waits for transitionend. Reduced motion removes that transition, including when the preference changes during interaction. The toggle and disclosure remain usable regardless of movement.

General polygon/path morphing needs compatible topology, winding, point/segment correspondence and an explicit interruption policy. Avoid naïvely interpolating unrelated path strings or switching a mask halfway through an action. If adopting a platform shape library, verify its actual supported shapes, interpolation contract and motion scheme against the target SDK/version. For web custom geometry, retain a stable content/control layer and equivalent text, then test endpoints, intermediate bounds, cancellation and reduced motion.

Implementation and acceptance

  • Keep rectangular hit areas and native controls separate from decorative outlines. Use at least the project’s 44px primary-action target while allowing larger text to grow.
  • Do not add ARIA to decorative geometry that repeats its caption. The lab’s meaningful model has a name/description and equivalent numeric text; a crop used only as decoration should remain hidden from assistive technology.
  • Use visible edges in both themes and forced colors. Test focus at every changed corner, not just the resting silhouette. Keep media clipping in an inner layer.
  • Check long text, 200% text enlargement, narrow layouts, logical direction, retained base/alternate settings and interrupted toggles. Inspect actual computed corner values and resulting geometry rather than a token name alone.
  • Record family consistency, intended emphasis, mapping scope and content ownership. Distinguish an authored web treatment from a native library implementation, a real photograph or qualified cultural review.

Compiled explorer

tsx
'use client';

import {useId,useState, type CSSProperties} from 'react';
import {Button} from '@mobtranslate/ui';
import catalogue from '../content/tokens.json';
import './shape-explorer.css';

const radii=catalogue.tokens.filter(token=>token.name.startsWith('--radius-'));
const corners=['borderStartStartRadius','borderStartEndRadius','borderEndStartRadius','borderEndEndRadius'] as const;
const initial=Object.fromEntries(corners.map(corner=>[corner,'--radius-lg']));

export function ShapeLab(){
  const id=useId();
  const [radius,setRadius]=useState('--radius-lg');
  const [alternate,setAlternate]=useState('--radius-2xl');
  const [individual,setIndividual]=useState(false);
  const [values,setValues]=useState(initial);
  const [rtl,setRtl]=useState(false);
  const [applied,setApplied]=useState(false);
  const [outer,setOuter]=useState(48);
  const [padding,setPadding]=useState(14);
  const inner=Math.max(0,outer-padding);
  const style=Object.fromEntries(corners.map(corner=>[corner,`var(${applied?alternate:individual?values[corner]:radius})`])) as CSSProperties;
  const options=radii.map(token=><option key={token.name} value={token.name}>{token.name} · {token.value}</option>);
  return <div className="shape-lab" data-shape-lab>
    <label>Container radius<select data-shape-radius className="mt-input" value={radius} onChange={event=>setRadius(event.target.value)}>{options}</select></label>
    <label className="shape-check"><input data-shape-individual type="checkbox" checked={individual} onChange={event=>setIndividual(event.target.checked)} />Set individual logical corners</label>
    {individual&&<fieldset><legend>Base logical corner tokens</legend><div className="shape-corners">{corners.map(corner=><label key={corner}>{corner}<select data-shape-corner={corner} className="mt-input" value={values[corner]} onChange={event=>setValues(previous=>({...previous,[corner]:event.target.value}))}>{options}</select></label>)}</div></fieldset>}
    <label className="shape-check"><input data-shape-rtl type="checkbox" checked={rtl} onChange={event=>setRtl(event.target.checked)} />Right-to-left corner mapping</label>
    <label>Alternate corner token<select data-shape-alternate className="mt-input" value={alternate} onChange={event=>setAlternate(event.target.value)}>{options}</select></label>
    <Button data-shape-toggle type="button" aria-pressed={applied} onClick={()=>setApplied(previous=>!previous)}>Toggle corner treatment</Button>
    <p role="status">{applied?'Alternate corners applied.':'Base corners applied.'} This is presentation state, not source review or an external action.</p>
    <div data-shape-surface dir={rtl?'rtl':'ltr'} style={style}>
      <h3 dir="auto">Source details</h3><p dir="auto">The container changes its corners. Content remains in the DOM; the disclosure retains its native semantics.</p>
      <details><summary>View source example</summary><p dir="auto">Original English interface specimen. A real entry would carry its own source and review records.</p></details>
    </div>
    <p>Changes use the current duration/easing tokens. Reduced motion removes the transition. Radius does not define a hit area, focus behavior or meaning; large corners can require more padding than this deliberately adjustable specimen.</p>
    <details data-shape-css><summary>Current corner CSS</summary><pre><code>{Object.entries(style).map(([name,value])=>`${name.replace(/[A-Z]/g,letter=>`-${letter.toLowerCase()}`)}: ${value};`).join('\n')}</code></pre></details>
    <fieldset><legend>Optical nesting and corner-area model</legend>
      <label>Outer radius · {outer} model units<input data-shape-outer-input type="range" min="0" max="64" value={outer} onChange={event=>setOuter(Number(event.target.value))} /></label>
      <label>Inset · {padding} model units<input data-shape-padding-input type="range" min="0" max="32" value={padding} onChange={event=>setPadding(Number(event.target.value))} /></label>
      <svg viewBox="0 0 200 200" role="img" aria-labelledby={`${id}-geometry-title ${id}-geometry-desc`}>
        <title id={`${id}-geometry-title`}>Rounded nesting and cut-corner comparison</title><desc id={`${id}-geometry-desc`}>Solid outer and inner rounded rectangles, dashed outer cut corners. Outer magnitude {outer}, inset {padding}, inner radius {inner}, in model units. Stroke centerlines are compared, not CSS border boxes.</desc>
        <rect data-shape-outer x="8" y="8" width="184" height="184" rx={outer} fill="none" stroke="currentColor" strokeWidth="2" />
        <path data-shape-cut d={`M ${8+outer} 8 H ${192-outer} L 192 ${8+outer} V ${192-outer} L ${192-outer} 192 H ${8+outer} L 8 ${192-outer} V ${8+outer} Z`} fill="none" stroke="currentColor" strokeWidth="2" strokeDasharray="4 3" />
        <rect data-shape-inner x={8+padding} y={8+padding} width={184-2*padding} height={184-2*padding} rx={inner} fill="none" stroke="var(--color-primary)" strokeWidth="2" />
      </svg>
      <p data-shape-optical>Inner radius = max(0, {outer} − {padding}) = {inner}. {padding>outer?'Inset exceeds the radius; the inner corner is square.':'The circular corners share their center when the inner radius is positive.'}</p>
      <p data-shape-area>At the same corner magnitude {outer}, four cut corners remove {(2*outer*outer).toFixed(2)} square model units; four circular rounded corners remove {((4-Math.PI)*outer*outer).toFixed(2)}. Both start from a square with non-overlapping corner regions. This compares removed area, not usable text padding.</p>
      <p>The drawing scales to available width. These are model coordinates, not device dp or guaranteed rendered CSS pixels. Browser normalization of oversized/full radii and real border thickness need separate inspection.</p>
    </fieldset>
  </div>;
}

Explorer stylesheet

css
.shape-lab { display: grid; gap: 1rem; min-inline-size: 0; }
.shape-lab label, .shape-lab fieldset { min-inline-size: 0; overflow-wrap: anywhere; }
.shape-lab .mt-input { display: block; inline-size: 100%; margin-block-start: .5rem; }
.shape-lab .shape-check { display: flex; align-items: center; gap: .75rem; min-block-size: 44px; }
.shape-check input { flex: 0 0 auto; }
.shape-corners { display: grid; grid-template-columns: repeat(auto-fit,minmax(min(100%,16rem),1fr)); gap: 1rem; }
.shape-lab fieldset { padding: 0; }
.shape-lab input[type=range] { display: block; inline-size: 100%; min-block-size: 44px; }
.shape-lab svg { inline-size: 200px; max-inline-size: 100%; block-size: auto; }
.shape-lab p, .shape-lab summary, .shape-lab code { overflow-wrap: anywhere; }
.shape-lab pre { white-space: pre-wrap; }
.shape-lab summary { min-block-size: 44px; }
.shape-lab [data-shape-toggle] { min-block-size: 44px; block-size: auto; white-space: normal; padding-block: .75rem; }
.shape-lab [data-shape-surface] { padding: 16px; border: 2px solid var(--color-muted-foreground); background: var(--color-card); color: var(--color-card-foreground); transition-property: border-start-start-radius, border-start-end-radius, border-end-start-radius, border-end-end-radius; transition-duration: var(--duration-normal); transition-timing-function: var(--ease-out); }
.shape-lab [data-shape-surface] h3 { color: inherit; font-weight: 600; }
@media(prefers-reduced-motion: reduce) { .shape-lab [data-shape-surface] { transition-property: none; } }
@media(forced-colors: active) { .shape-lab [data-shape-surface] { border-color: CanvasText; } .shape-lab svg rect, .shape-lab svg path { stroke: CanvasText; } }