Skip to content

Components

Language: JavaScript

The component library ships atoms and molecules: themeable, accessible primitives built on React Native Web. The library is own code, following Superloom's loader pattern and Lib DI throughout. Design languages arrive as styler template packs, not as separate component libraries. This page defines the component vocabulary, the authoring contract, the three-bucket exception model, and the accessibility contract.

On This Page


Component Vocabulary

The library uses two levels from Brad Frost's atomic design taxonomy. The upper hierarchy (organisms, templates, pages) is deliberately excluded.

LevelDefinitionBoundary
AtomAn irreducible primitive wrapping one RN element with token consumption and accessibility behaviorNo composition of other library components. No domain knowledge
MoleculeA composition of atoms with interaction logicNo domain knowledge. Composes atoms only; never composes other molecules

Organisms and above are not library concepts. Anything domain-aware (a product card, a cart summary, a checkout form) is an app-side screen component or an app-registered variant. It never ships in the component library. This bounds the library and answers the question: there is no organism/ folder because organisms are app concerns.

The three-bucket exception model (below) replaces the upper hierarchy for handling components that deviate from the canonical set.


Atom Set

The canonical atom set wraps primitive React Native elements. Each atom maps props to utility classes and applies accessibility behavior.

AtomRN elementKey props
ViewViewLayout, background, padding, margin
TextTextSize, color, weight, family
ButtonPressableVariant, size, state, onPress, accessibilityLabel
IconText (vector icon)Name, size, color
ImageImageSource, resize, aspect
TextInputTextInputValue, placeholder, state, accessibilityLabel
SwitchSwitchValue, onValueChange, state
BadgeViewCount, color, position
SeparatorViewOrientation, color
ProgressIndicatorView (animated)Value, color, size

Adding an atom is a library change. The atom must follow the authoring contract, consume tokens through utility classes, and include accessibility behavior.


Molecule Set

Molecules compose atoms with interaction logic. A molecule coordinates state across its child atoms but carries no domain knowledge.

MoleculeComposesInteraction
ButtonPrimaryIcon + Text + ButtonHover/press/disabled state resolution, icon + label layout
DropdownButton + View + TextOpen/close state, selection, accessibility focus management
ModalView + Text + ButtonVisibility state, backdrop, focus trap
CardView + Text + ImageLayout, optional press state
ListItemView + Text + Icon + SeparatorSelection, swipe actions, accessibility role

Adding a molecule is a library change. The molecule must compose atoms only, consume tokens through utility classes, and include accessibility behavior for its interaction pattern.


Authoring Contract

Every component is a loader module. The library entry point is combineComponent(Lib, theme), which builds the themed component set.

The contract:

  1. combineComponent(Lib, theme) generates CommonStyle (the utility style map for the current theme), builds the HOC, and wires every component via make(factory)
  2. Each component factory is function (Component, CommonStyle, theme, Lib) returning a React component
  3. The component maps props to utility classes: size to font_size_[step], color to font_[token], weight to font_weight_[name]
  4. Molecules compose atoms through the shared Component object, not through direct require
  5. Atoms strip isRtlActive so it never leaks to the DOM on web

The HOC (componentHoc.js) injects isRtlActive into every component. Atoms consume it for directional logic and strip it from props before rendering. Molecules consume it and pass it to child atoms.

updateComponentTheme(newTheme) re-derives CommonStyle and re-wires every component without unmounting the tree. This is the runtime re-theming mechanism.


Utility-Class Mapping

Components read named utility classes rather than inline token lookups. The mapping is deterministic:

PropUtility classExample
sizefont_size_[step]size="md" to font_size_md
colorfont_[token]color="text_primary" to font_text_primary
weightfont_weight_[name]weight="semibold" to font_weight_semibold
backgroundbackground_[token]background="app_primary" to background_app_primary
paddingp_[side]_[step]padding="a_md" to p_a_md
marginm_[side]_[step]margin="t_lg" to m_t_lg
radiusbr_[step]radius="pill" to br_pill

Spacing utilities use logical sides (s/e) for RTL. See Theming for the full utility style reference.


Three-Bucket Exception Model

Real apps need disciplined deviation and a clean way to abandon the token system entirely. The three-bucket model handles both.

BucketLocationToken accessRegistryRe-themes
Canonicalatom/ or molecule/Full token access via CommonStyleComponent.[name]Yes
Structured variantvariant/Full token access via CommonStyleComponent.variant.[name]Yes
Unstructured freeformfreeform/No token access. No CommonStyle. Raw styles onlyComponent.freeform.[name]No

Canonical

The default. Atoms and molecules reading tokens through utility classes. This is the normal case.

Structured variant

A different composition of the same atoms with the same tokens. Example: an outlined button variant shares Button + Text atoms but changes the background and border resolution. The variant is registered in Component.variant so it is discoverable. It re-themes when the theme changes because it reads the same CommonStyle.

Unstructured freeform

A component that opts out of the token system entirely. It receives no CommonStyle, no theme, no tokens. It takes raw styles only. It lives in Component.freeform, a fenced namespace. It does not re-theme.

The freeform bucket exists for components that cannot conform: a marketing hero, a chat bubble, a one-off animation. A future lint rule flags imports from freeform/ so its use is a conscious decision.

The rules are testable constraints:

  • A canonical component must not import from variant/ or freeform/
  • A variant component must register in Component.variant
  • A freeform component must not receive CommonStyle or theme
  • A freeform component must not appear outside Component.freeform

Interaction States

Every interactive component supports five states. The state names align with Carbon's interaction-state specifications.

StateMeaningVisual treatment
enabledDefault resting stateBase token values
hoveredPointer over the componentpseudoHover color operation (lightens dark colors, darkens light ones)
pressedComponent is being pressedpseudoPress color operation (stronger shift than hover)
focusedComponent has keyboard or screen-reader focusFocus ring or outline
disabledComponent is non-interactivedisabled color operation (45% original + 55% white)

The styler engine derives pseudo-state colors from base colors through the template's color operations. Components resolve the active state from interaction events and select the corresponding utility class or token.

The focused state is the accessibility-visible state. It must render a visible focus indicator on every platform, including web (keyboard navigation) and native (VoiceOver/TalkBack focus).


Accessibility Contract

Components meet the accessibility contract through React Native's built-in accessibility props, which React Native Web maps to DOM ARIA attributes.

RequirementImplementation
RolesaccessibilityRole prop on every interactive component. Maps to ARIA role on web
LabelsaccessibilityLabel on every component lacking a visible text label
State announcementaccessibilityState for selected/expanded/disabled states
Focus managementMolecules that open/close (Modal, Dropdown) trap focus and restore on close
Hit targetMinimum 44x44 points on interactive components (iOS HIG), 48x48 dp (Android Material)
Focus indicatorVisible focus ring or outline in the focused state

The accessibility patterns follow gluestack's headless component approach as a study reference: behavior and accessibility logic are separated from visual styling, so the same component works with any template pack. The library implements these patterns in its own code rather than wrapping gluestack.


Generic vs Custom

The library ships generic atoms and molecules. Apps register their own variants and freeform components alongside the generic set.

Example: a restaurant suite has a POS application and a customer ordering application. Both share the same atom set (Text, Button, Icon, Image). The POS uses large-touch variant buttons (a structured variant with bigger hit targets and higher contrast). The ordering app uses the canonical button. Both variants read the same tokens; they differ at the molecule and variant layer.

App-registered variants live in the app's source, not in the library. The library provides the generic set and the extension points (Component.variant, Component.freeform). The app populates them.


Peer Dependencies

The component library declares its runtime dependencies as peer dependencies. The host app (the Expo project at src/client/) provides them.

DependencyWhy it is a peer
reactThe host owns the React version
react-nativeThe host owns the RN version (via Expo SDK)
@expo/vector-iconsThe host owns the icon set

The library never bundles these. Test apps inside the library pin real versions as dev dependencies for isolation.


Further Reading

Released under the MIT License.