MobTranslate styleguide

Composition

Combine styling, semantics and behavior while keeping one operable control for each action.

Choose a semantic element

Use a link for navigation and a button for an action. Styling a link to look like a button does not make it a submit control. Avoid nesting interactive controls inside each other; a button inside a trigger button creates conflicting keyboard and pointer behavior.

NeedUseAvoid
Navigate to a source routeAn anchor or router link with hrefA div that sets location on click
Submit a formButton type="submit"A link with a submit-like label
Expand detailsCollapsibleTrigger with its panelA clickable heading without expanded state
Open an overlayThe overlay’s trigger partA second independently managed focus trap
Group contentNon-interactive layout surfaceMaking every card a button because it has a shadow

Base UI render composition

Base UI parts exposed by this package accept their primitive’s render prop. The trigger supplies behavioral props to the rendered element. In the checked example, CollapsibleTrigger renders the library Button rather than wrapping another button inside it.

Current upstream Base UI documentation uses the newer @base-ui/react package name. This workspace is pinned to @base-ui-components/react 1.0.0-rc.0. Read the installed types and this wrapper’s API before copying newer upstream imports or options.

Example details collapsed.

tsx
'use client';

import { useState } from 'react';
import { Button, Collapsible, CollapsiblePanel, CollapsibleTrigger } from '@mobtranslate/ui';

export function CompositionExample() {
  const [open, setOpen] = useState(false);
  return <Collapsible open={open} onOpenChange={setOpen}>
    <CollapsibleTrigger render={<Button variant="outline" type="button" />}>
      Example source details
    </CollapsibleTrigger>
    <CollapsiblePanel>
      <p className="mt-4">This is interface placeholder text, not an attested source record.</p>
    </CollapsiblePanel>
    <p role="status" className="mt-3 text-sm">Example details {open ? 'expanded' : 'collapsed'}.</p>
  </Collapsible>;
}

Forward props and refs

  • A custom render component must forward the ref to the actual operable DOM element and pass through supplied ARIA attributes, event handlers and state attributes.
  • Do not replace a primitive’s onKeyDown or onClick without preserving its behavior. Consult the installed version’s event composition contract.
  • Keep accessible names on the real control. Decorative icons should not add duplicate announcements.
  • Preserve stable keys during reorder so React retains the intended element identity. Do not key mutable lists by position.

Controlled and uncontrolled state

Use controlled open/value with the matching change handler when the surrounding task owns the state. Use defaultOpen/defaultValue for an uncontrolled initial state. Do not switch modes mid-lifecycle or initialize controlled text inputs with undefined.

StateOwnerVerification
Uncontrolled disclosurePrimitive initialized by defaultOpenCan open and close without a parent update
Controlled disclosureParent open + onOpenChangeParent reflects the requested new state
Validated inputForm/application stateError, retained value and accessible description agree
Pending requestRequest lifecycleDuplicate submission is prevented; cancel/recovery remains available
Persistent resultBackend plus visible application stateVerify the actual stored outcome, not just a local success label

Forms and native props

  • Set button type explicitly inside forms. A custom action should usually use type="button" unless it intentionally submits.
  • Input size selects the component size; htmlSize controls the native input size attribute. Do not conflate these APIs.
  • Connect labels and descriptions to the input. A wrapper’s visual error style does not by itself announce an error.
  • Verify submitted names and values from the actual form. Not every styled selection widget is automatically a native successful form control.

Portal ownership

A portal keeps React ownership but changes DOM placement. Theme, direction and scoped styles follow the DOM. Overlay primitives also manage focus and dismissal; do not add a second independent global handler that fights them.

  • Give dialogs a real title and description when the description is useful.
  • Restore focus to a surviving trigger or another deliberate destination after close.
  • Check nested overlays, Escape behavior and scroll containment.
  • A visual backdrop is not proof that background interaction and focus are correctly controlled.

Current wrapper limits

Button asChild now forwards DOM props, merges handlers/styles/classes and both refs through the installed Base UI utilities, and enforces disabled/loading activation blocking. It preserves the child’s element semantics. Do not assume every other wrapper implements asChild; primitive render remains the appropriate composition path for Base UI parts.

Some exports are native styled elements and others are Base UI parts. Their composition APIs differ. Check the actual exported type instead of applying one wrapper recipe to every component.

Verification

  • Inspect the checked disclosure: exactly one trigger button, a changing aria-expanded value and a panel linked to that trigger.
  • Activate with Enter, Space and pointer input. Controlled state and the local status should agree.
  • Inspect a composed control’s accessible name and forwarded ref/focus behavior.
  • Test form submission, overlay close and any default browser action separately. A visual click response does not prove the whole task.