MobTranslate styleguide

Design handoff and usability review

Document the task, semantic structure, focus flow and element contracts, then evaluate whether the resulting experience helps people complete it.

Two complementary questions

Accessibility asks whether people can perceive, understand and operate the experience with their chosen setup. Usability also asks whether they can find the relevant action, understand its consequence and complete the task effectively. A technically named button can still have confusing wording or inappropriate prominence.

Begin with the person’s goal and the actual product outcome. For a source lookup, returning a record, inspecting evidence and recovering from a failed replacement are distinct outcomes. A visually polished result is not proof of qualified review or successful publication.

Review lensEvidenceLimitation
Semantic and technicalRoles, names, relationships, keyboard paths and state handlingDoes not prove comprehension
Task usabilityObserved completion, errors, recovery and participant explanationDoes not replace technical accessibility checks
Content authoritySource, review mandate and permitted audienceDoes not follow from attractive styling
Performance of the taskTime and effort in the agreed contextSpeed alone can conceal misunderstanding

Prepare an accessibility annotation

A handoff should explain behavior that a still image cannot show. Attach annotations to stable region/control identities and include the default plus applicable waiting, failure, success and recovery states. Keep design intent and verified implementation results in separate fields.

AnnotationSpecifyImplementation evidence
StructureHeading levels, landmarks and region purposeActual DOM hierarchy and named repeated landmarks
FlowEntry, traversal, activation, dismissal and restorationNative keyboard sequence and focus coordinates
ElementRole, visible label, accessible name and value/stateComputed accessibility tree and actual value changes
RelationshipLabels, help, errors, grouping and ownershipResolved IDs and current DOM associations
FeedbackTrigger, wording, urgency and whether focus movesExact state change and live-region strategy
AdaptationNarrow space, enlarged text, direction and motionRendered task with complete recovery
Open issueImpact, owner and acceptance operationRepair tested in the same task/context

Structure, hierarchy and grouping

  • Make the document hierarchy express the information hierarchy. Use one clear page purpose, descriptive section headings and appropriate landmarks; do not choose heading levels for their default font size.
  • Name multiple navigation regions so people can distinguish primary destinations, record ancestry and related links. An adjacent pane does not automatically need another main landmark.
  • Use lists for related items, tables for meaningful row/column relationships and fieldsets/legends for related choices. A border alone cannot expose a programmatic group.
  • Separate persistent task information from transient feedback. Put the status or error near its affected operation without repeatedly announcing an entire page.
  • Use spacing and containment to show relationships. Keep a source qualification with its record when layout changes.

Color, contrast and clustered controls

Annotate role pairs and applicable states, rather than one attractive default swatch. Text, required boundaries, selection and focus need their own checks against the actual composed background. Color cannot be the only distinction between a draft and an approved record.

When controls cluster, inspect the whole grouping: boundary contrast, labels, target separation and focus ring clearance. A dense group with individually adequate colors can still make the active control hard to locate. Use the existing contrast tool for opaque pairs and rendered verification for layered surfaces.

SituationDesign responseVerify
Closely spaced actionsClear names, hierarchy and independent target/focus spacePointer cancellation, keyboard focus and neighboring targets
Selected recordStable selected/current indication plus its actual detailName/state and record identity agree
ErrorField-specific text and associated correctionThe message is reachable without color
OverlayIntentional layering and suitable foreground/backgroundPortalled theme and focus visibility
Muted helpReadable complete supporting textActual contrast, not an assumed disabled-state exception

Flow, defaults and key traversal

Start from native document and control behavior. Tab traverses focusable controls; composite widgets can have their documented internal key model. A custom shortcut must have a real advantage, clear scope and a way to avoid conflicts with text input, browser commands and assistive technology.

Define the initial entry point, meaningful defaults, submission, interruption and return. A default can reduce effort when it is appropriate and reversible; it cannot stand in for a person’s permission or a qualified review. Keep a disabled or removed focused control from stranding the task.

EventFocus/operation contract
Page arrivalA meaningful heading and reachable first task control
Invalid submissionNamed summary or first error; retained values and correction links
WaitingReachable cancellation where supported; duplicate effects guarded
Ordinary completionDiscoverable result without unnecessary focus theft
Overlay open/closeNamed context, appropriate containment and surviving return target
Record/layout changeRetained identity and deliberate recovery if a focused region disappears
ShortcutDocument its modifier, scope, conflicts and equivalent visible action

Element names and contextual labels

Keep names concise and specific to the action. Native roles already communicate control type, so do not normally append “button” or “checkbox” to the name. Help text may be essential without becoming part of the name; use the appropriate description relationship. Inspect the computed tree because DOM attributes alone do not prove the final name.

ElementNaming contractFailure
Text buttonVisible action wording normally supplies its nameReplacing Save draft with unrelated aria-label text
Icon actionName the operation or destinationAnnouncing only a glyph name or “button button”
Repeated actionInclude enough record context to distinguish itSeveral indistinguishable Open buttons
InputPersistent label; separate associated help and errorPlaceholder used as the only name
Meaningful imageAlternative for its contextual purposeFilename or guessed source identification
Decorative companionRemove redundant semantics when adjacent text carries meaningRepeating the action name from both icon and button
State/valueExpose current checked, selected, expanded or numeric valueWriting the state into a label while the actual property disagrees

Worked request handoff

This existing compiled English fixture gives the handoff a real implementation. Submit empty fields, follow the error links, enter values and exercise the requested local outcome. Compare the actual error, waiting, cancellation and completion behavior with the contracts below. It does not submit to a remote service.

Region/controlDefault contractChanged state
Task formPersistent labels and explanatory textValues retained through validation/recovery
Submit actionNamed native actionDuplicate submission guarded while the request is pending
Error summaryAbsent until explicit invalid submissionReceives focus and links to the invalid fields
Help/error textAssociated with its inputCorrection remains available while needed
Local requestOutcome is explicitly a fixtureCancellation/failure/completion do not imply a remote write
Result feedbackPersistent polite status strategyReturns to an enabled action without an automatic expiry

This local request never contacts a service. Choose a simulated outcome; there is no countdown.

Use your own English text to try the interaction.

Copyable handoff template

Fill the placeholders with actual source/control IDs, behavior and evidence. The pending fields are intentional: copying a template is not an executed review.

json
{
  "status": "unexecuted design handoff",
  "task": "Complete the agreed task",
  "build": "record exact build/route",
  "structure": [
    {
      "id": "replace-with-region-id",
      "purpose": "task region",
      "semantics": "choose native heading/landmark/group"
    }
  ],
  "elements": [
    {
      "id": "replace-with-control-id",
      "role": "native control role",
      "visibleLabel": "actual wording",
      "accessibleName": "expected computed name",
      "descriptionIds": [],
      "applicableStates": [
        "default",
        "waiting",
        "error",
        "recovery"
      ],
      "focusOnEntry": "define",
      "focusOnExit": "define",
      "equivalentAction": "define when a gesture/shortcut exists"
    }
  ],
  "observations": [],
  "verification": {
    "keyboard": "pending",
    "accessibilityTree": "pending",
    "visualAdaptation": "pending",
    "participantTaskReview": "not conducted"
  },
  "issues": []
}

Usability tactics with a purpose

Choose a tactic because it supports the agreed task, not because every expressive technique must appear on one screen. Keep scale, placement, color roles and grouping consistent through the task’s states. In this library, the committed Earth palette and tokens remain the baseline; a usability chapter is not an instruction to rebrand the product.

TacticTask purposeFailure to avoid
ColorMaintain role consistency and guide attentionA new palette is treated as proof of improved usability
ContainmentGroup related controls and outcomesEverything becomes nested cards
MotionExplain a transition or continuityMovement competes with reading or bypasses reduced motion
Shape/morphingReinforce a meaningful action/state relationshipAn unfamiliar shape conceals the action’s identity
SizeEmphasize the main action while preserving targetsAll controls scale indiscriminately
TypographyDifferentiate task heading, content and helpA decorative type treatment reduces legibility

Evaluate and iterate

Use a concrete task with a known starting state and an observable outcome. Ask people to complete it with their preferred interaction setup; record observed action and explanation separately from a designer’s interpretation. Include discovery, error recovery and delayed/offline behavior when those belong to the task.

Compare candidate changes against the same task and constraints. Define what would count as an improvement before seeing results: fewer wrong actions, clearer consequence understanding, successful recovery or reduced unnecessary effort. A shorter completion time with more misunderstanding is not automatically better.

  • Agree participation, retention and quotation choices; use the Inclusive Participation process.
  • Capture enough context to reproduce a barrier without collecting unnecessary identifying data.
  • Record unresolved effects and a repair owner. Retest the actual repaired task rather than accepting a new screenshot.
  • Use multiple kinds of evidence. Automated checks, technical walkthroughs and participant observations answer different questions.
  • A lab or unexecuted plan is not a human study. Publish only the scope actually conducted.

Implementation and verification

  • Verify native roles, names, state and description relationships in the rendered task.
  • Follow Tab and reverse Tab through validation, pending, cancellation and recovery; inspect unobscured focus.
  • Check both themes, long labels, 320px reflow, text enlargement, spacing overrides and relevant reduced-motion/forced-color behavior.
  • Compare the actual task result with every message and handoff annotation.
  • Use a named browser/assistive-technology pair before claiming spoken behavior.
  • Record the exact subject/state coverage and remaining findings; do not label a generic green scan as a whole-product usability result.
tsx
'use client';

import { useEffect, useId, useRef, useState } from 'react';
import { Button, Input } from '@mobtranslate/ui';

const actionStyle = {minHeight:44,height:"auto",minWidth:0,maxWidth:"100%",whiteSpace:"normal",overflowWrap:"anywhere",paddingInline:".5rem"} as const;

export function AccessibleRequestExample() {
  const id = useId();
  const [title, setTitle] = useState('');
  const [note, setNote] = useState('');
  const [errors, setErrors] = useState<string[]>([]);
  const [phase, setPhase] = useState<'editing' | 'pending' | 'failed' | 'complete'>('editing');
  const [announcement, setAnnouncement] = useState('');
  const [invalidAttempt, setInvalidAttempt] = useState(0);
  const summary = useRef<HTMLDivElement>(null);
  const submit = useRef<HTMLElement>(null);
  useEffect(() => { if (invalidAttempt) summary.current?.focus(); }, [invalidAttempt]);
  const pending = phase === 'pending';
  function edit() { setPhase('editing'); setAnnouncement(''); }
  function finish(next: 'editing' | 'failed' | 'complete', message: string) {
    setPhase(next);
    setAnnouncement(message);
    submit.current?.focus();
  }
  return <div data-accessibility-lab className="space-y-4">
    <p>This local request never contacts a service. Choose a simulated outcome; there is no countdown.</p>
    <form noValidate className="space-y-3" onSubmit={event => {
      event.preventDefault();
      if (pending) return;
      const invalid = [!title.trim() && 'title', !note.trim() && 'note'].filter((field): field is string => Boolean(field));
      setErrors(invalid);
      if (invalid.length) {
        setAnnouncement('');
        setPhase('editing');
        setInvalidAttempt(attempt => attempt + 1);
        return;
      }
      setPhase('pending');
      setAnnouncement('Local request pending. Choose an outcome or cancel below.');
    }}>
      {errors.length > 0 && <div ref={summary} tabIndex={-1} aria-labelledby={`${id}-summary`} className="border-2 border-[var(--color-destructive)] rounded-lg p-4">
        <h3 id={`${id}-summary`} className="font-bold">Check {errors.length} {errors.length === 1 ? 'field' : 'fields'}</h3>
        <ul className="guidance-list">{errors.map(field => <li key={field}><a href={`#${id}-${field}`} onClick={event => {
          event.preventDefault();
          document.getElementById(`${id}-${field}`)?.focus();
        }}>{field === 'title' ? 'Title: enter a title.' : 'Note: enter a note.'}</a></li>)}</ul>
      </div>}
      <label htmlFor={`${id}-title`} className="block font-medium">Title (required)</label>
      <Input id={`${id}-title`} name="title" required readOnly={pending} value={title} state={errors.includes('title') ? 'error' : 'default'}
        aria-describedby={`${id}-help${errors.includes('title') ? ` ${id}-title-error` : ''}`} onChange={event => {
          setTitle(event.target.value); setErrors(current => current.filter(field => field !== 'title')); edit();
        }} />
      <p id={`${id}-help`} className="text-sm">Use your own English text to try the interaction.</p>
      {errors.includes('title') && <p id={`${id}-title-error`}>Enter a title.</p>}
      <label htmlFor={`${id}-note`} className="block font-medium">Note (required)</label>
      <textarea id={`${id}-note`} name="note" className="mt-textarea w-full" required readOnly={pending} value={note}
        aria-invalid={errors.includes('note') || undefined} aria-describedby={errors.includes('note') ? `${id}-note-error` : undefined}
        onChange={event => { setNote(event.target.value); setErrors(current => current.filter(field => field !== 'note')); edit(); }} />
      {errors.includes('note') && <p id={`${id}-note-error`}>Enter a note.</p>}
      <Button style={actionStyle} ref={submit} type="submit" aria-disabled={pending || undefined} aria-busy={pending || undefined}>Try accessible request</Button>
    </form>
    <div className="flex flex-wrap gap-3">
      <Button style={actionStyle} type="button" variant="secondary" disabled={!pending} onClick={() => finish('complete', 'Local example complete. Your text is retained; nothing was sent.')}>Simulate completion</Button>
      <Button style={actionStyle} type="button" variant="outline" disabled={!pending} onClick={() => finish('failed', 'Simulated connection failure. Your text is retained. Try accessible request again to retry.')}>Simulate failure</Button>
      <Button style={actionStyle} type="button" variant="ghost" disabled={!pending} onClick={() => finish('editing', 'Local request cancelled. Your text is retained.')}>Cancel local request</Button>
    </div>
    <p role="status" aria-atomic="true" className="min-h-6">{announcement}</p>
  </div>;
}