Module: React and Ecosystem
React and Ecosystem·112·8 MIN READ

112: Component Architecture, Headless Patterns, Styling, Animation, and Internationalization

TOPICS COVERED: Component Architecture, Headless Patterns, Styling, Animation, and Internationalization

Learning objectives

You will learn to:

  • design reusable component APIs through composition;
  • understand compound components;
  • recognize render props and HOCs in existing code;
  • build headless behavior separated from styling;
  • choose styling approaches deliberately;
  • use component libraries without losing semantics/accessibility;
  • add motion without breaking reduced-motion preferences;
  • design UI for localization rather than hard-coded English layout assumptions.

Component API design

A reusable component is a small library.

Its API should answer:

  • what does the caller control?
  • what does the component own?
  • what are valid child structures?
  • how are events reported?
  • what accessibility responsibilities are built in?

Compound components

Example API:

jsx
<Tabs
  value={tab}
  onValueChange={
    setTab
  }
>
  <Tabs.List
    aria-label="Task views"
  >
    <Tabs.Trigger
      value="open"
    >
      Open
    </Tabs.Trigger>

    <Tabs.Trigger
      value="done"
    >
      Done
    </Tabs.Trigger>
  </Tabs.List>

  <Tabs.Panel
    value="open"
  >
    <OpenTasks />
  </Tabs.Panel>

  <Tabs.Panel
    value="done"
  >
    <CompletedTasks />
  </Tabs.Panel>
</Tabs>

Compound APIs can make related pieces discoverable while still allowing composition.

Internally they often use Context.

Do not use compound components for every two-element component.

Headless components

A headless component/hook owns behavior and accessibility while letting consumers choose visual styling.

Examples:

  • combobox behavior;
  • menu keyboard interaction;
  • dialog focus behavior;
  • tabs selection model.

This is useful because sophisticated accessibility behavior is difficult to rebuild repeatedly.

Headless libraries should not be adopted blindly. Audit:

  • keyboard model;
  • ARIA semantics;
  • bundle impact;
  • portal behavior;
  • controlled/uncontrolled support;
  • styling integration.

Render props

You may encounter:

jsx
<DataLoader>
  {({
    data,
    pending,
  }) => (
    ...
  )}
</DataLoader>

Render props were a common pre-Hooks composition mechanism and remain useful in some libraries.

Do not rewrite them merely because Hooks exist if the API remains clear and stable.

Higher-order components

Legacy/library code may contain:

jsx
const Enhanced =
  withPermissions(
    TaskPage,
  );

An HOC receives a component and returns a component.

Modern application code often prefers Hooks/composition, but understanding HOCs is necessary for maintaining older React ecosystems.

Watch for:

  • wrapper stacks;
  • lost static properties;
  • prop collisions;
  • confusing DevTools trees.

Controlled component API

A reusable controlled component often uses:

jsx
value
onValueChange

An uncontrolled version may use:

jsx
defaultValue

This mirrors browser control conventions.

Avoid trying to support both without a deliberate contract.

Slot/composition pattern

Rather than boolean options:

jsx
<Card
  showHeader
  showActions
  showFooter
/>

prefer composition:

jsx
<Card>
  <Card.Header>
    Tasks
  </Card.Header>

  <TaskList />

  <Card.Footer>
    <TaskCount />
  </Card.Footer>
</Card>

This reduces prop explosion.

Styling options

React does not require one styling system.

Common choices:

Plain CSS

Strong baseline:

jsx
import './TaskCard.css';

CSS Modules

Scoped class names:

jsx
import styles
  from './TaskCard.module.css';

function TaskCard() {
  return (
    <article
      className={
        styles.card
      }
    >
      ...
    </article>
  );
}

Utility CSS

Libraries such as Tailwind can make design tokens and responsive utility composition convenient.

Trade-offs include class-heavy markup and tooling conventions.

CSS-in-JS

Useful in some design systems, but evaluate:

  • runtime cost;
  • server rendering behavior;
  • style insertion;
  • compiler/build integration.

Do not choose based on trend alone.

Component libraries

Libraries can accelerate:

  • accessible primitives;
  • complex form controls;
  • data display;
  • theming.

But a library does not replace product semantics.

Audit generated DOM.

If a visual “button” renders a non-interactive <div>, the abstraction is wrong for accessibility.

Styling state

Prefer state reflected through attributes/classes:

jsx
<button
  aria-pressed={
    selected
  }
  data-state={
    selected
      ? 'selected'
      : 'idle'
  }
>

CSS:

css
button[data-state="selected"] {
  font-weight: 700;
}

This keeps visual rules in CSS and state rules in React.

Animation

Start with CSS for simple transitions.

Use a React animation library when you need:

  • enter/exit orchestration;
  • layout animations;
  • gesture animation;
  • spring physics;
  • complex coordinated sequences.

Do not animate purely because a library is installed.

Reduced motion

Respect:

css
@media (
  prefers-reduced-motion:
  reduce
) {
  *,
  *::before,
  *::after {
    animation-duration:
      0.001ms !important;
    animation-iteration-count:
      1 !important;
    scroll-behavior:
      auto !important;
  }
}

For important JavaScript-driven motion, detect the preference through an appropriate media-query abstraction.

Reduced motion means reducing unnecessary motion, not removing all visual feedback.

Accessibility architecture

Build accessibility into component contracts.

Button:

jsx
<button
  type="button"
  aria-pressed={
    active
  }
>

Field:

jsx
<label htmlFor={id}>
...
</label>

Dialog:

  • accessible name;
  • focus entry;
  • focus return;
  • dismiss behavior;
  • background interaction handling.

Do not add ARIA where native HTML already provides the correct semantics.

Internationalization

Hard-coded string concatenation causes localization problems:

jsx
<p>
  You have
  {' '}
  {count}
  {' '}
  tasks
</p>

Plural rules differ across languages.

Use an i18n formatter/library that supports message pluralization.

Also design for:

  • longer translated strings;
  • right-to-left layout;
  • locale-sensitive dates/numbers;
  • time zones;
  • language changes.

Use Intl for locale-sensitive primitives where appropriate.

Component ownership and design system boundaries

A design system component should not import business API code.

Bad:

text
shared/ui/Button
→ imports taskApi

Good:

text
feature task action
→ composes shared Button

Dependencies should point from feature code toward reusable primitives, not the other way around.

Common mistakes

  • giant configurable components with dozens of boolean props;
  • rebuilding complex ARIA widgets from scratch without understanding keyboard patterns;
  • mixing server calls into shared UI primitives;
  • styling every state inline;
  • assuming English text length;
  • animation that ignores reduced-motion preference;
  • using divs as buttons;
  • adopting multiple styling systems without design rules.

Exercises

  1. Build controlled Tabs with compound components.
  2. Convert a prop-heavy Card to composition.
  3. Style a component using CSS Modules.
  4. Audit a headless dialog for focus and accessible name.
  5. Add reduced-motion behavior.
  6. Format task dates using locale-aware Intl.DateTimeFormat.
  7. Draw a dependency diagram for shared UI versus feature code.

Exit questions

  1. What makes a component “headless”?
  2. Why are compound components useful?
  3. What are render props and HOCs?
  4. How should styling-system choice be evaluated?
  5. Why is reduced motion a component responsibility?
  6. Why should design-system components not import business APIs?

Official references


Deep dive: reusable component design is about invalid states

A good component API makes common valid use easy and invalid combinations difficult.

Poor:

jsx
<Modal
  open
  closed={false}
  hasHeader
  noHeader={false}
  dismissable
  noOverlay={false}
  type="confirmation"
  destructive
/>

Too many overlapping switches.

Better:

jsx
<Dialog open={open} onOpenChange={setOpen}>
  <Dialog.Content>
    <Dialog.Title>Delete task?</Dialog.Title>
    <Dialog.Description>
      This cannot be undone.
    </Dialog.Description>
    <Dialog.Actions>
      <Button variant="ghost">Cancel</Button>
      <Button variant="danger">Delete</Button>
    </Dialog.Actions>
  </Dialog.Content>
</Dialog>

Composition constrains structure through meaningful parts.

Polymorphic components

Design systems sometimes support:

jsx
<Button asChild>
  <Link to="/tasks">Tasks</Link>
</Button>

or an as prop.

Be careful.

Changing host element changes:

  • semantics;
  • keyboard behavior;
  • required props;
  • ref type;
  • accessibility.

Do not make everything polymorphic by default.

A <button> and <a> are not interchangeable merely because they look alike.

Headless state machines

A headless menu may manage:

text
closed
open
active item
keyboard navigation
typeahead
focus return
outside click
escape
portal
disabled items

That is substantial behavior.

Using Radix, React Aria, Headless UI, Ark UI, or similar mature primitives can be safer than reimplementing complex ARIA patterns.

But audit the specific library/version and understand its DOM contract.

Component library evaluation checklist

Before adopting:

  • React 19 compatibility;
  • SSR/hydration behavior;
  • accessibility claims/evidence;
  • bundle/tree shaking;
  • controlled/uncontrolled APIs;
  • styling model;
  • portal layering;
  • RTL support;
  • form integration;
  • animation/reduced motion;
  • maintenance/security health.

Do not choose based only on screenshot appearance.

Styling architecture

Plain/global CSS

Good for:

  • design tokens;
  • layout primitives;
  • small apps.

Risk:

  • global naming collisions without conventions.

CSS Modules

Good for local component styles.

jsx
import styles from './TaskCard.module.css';

Utility classes

Good for consistent token-based composition and rapid UI.

Need conventions for:

  • long conditional class lists;
  • reusable variants;
  • design tokens.

CSS-in-JS

Evaluate runtime versus build-time approaches.

Runtime style insertion can interact with:

  • SSR;
  • streaming;
  • style ordering;
  • useInsertionEffect.

Modern tooling may extract styles at build time.

Variant management

Instead of string concatenation spread across components, design reusable variant helpers if utility styling is used.

Example conceptual API:

jsx
buttonClass({
  variant: 'danger',
  size: 'sm',
  loading: true,
});

Keep visual variant logic separate from business authorization.

Button hidden/disabled due permission is business UI state; color variant is design-system state.

Design tokens

Use CSS variables:

css
:root {
  --color-surface: ...;
  --color-danger: ...;
  --space-2: .5rem;
  --radius-md: .5rem;
}

React should not own static token state.

Theme switching can toggle an attribute/class:

jsx
<html data-theme="dark">

CSS handles values.

Animation and presence

Animating mount/unmount is harder than static transitions because the DOM node disappears.

Animation libraries may provide presence primitives.

Architecture questions:

  • should exit animation delay unmount?
  • what happens if state flips during exit?
  • focus if dialog is closing?
  • reduced motion?
  • route navigation interruption?

Motion is behavior, not decoration only.

View Transitions and React ecosystem

Modern browsers/React ecosystem increasingly expose view-transition capabilities.

Use progressive enhancement.

Do not make navigation correctness depend on an animation API.

Reduced-motion users should receive a stable experience.

Internationalization details

Never store formatted display strings as canonical data.

Store:

text
ISO timestamp / numeric value / message key/domain state

Format at presentation:

jsx
new Intl.DateTimeFormat(locale, {
  dateStyle: 'medium',
  timeStyle: 'short',
}).format(date);

Currency:

jsx
new Intl.NumberFormat(locale, {
  style: 'currency',
  currency,
}).format(amount);

Do not hard-code:

jsx
₹${amount}

if product supports multiple locales/currencies.

RTL

Use logical CSS:

css
margin-inline-start
padding-inline
inset-inline-end

instead of always left/right.

Test icons whose direction carries meaning:

  • back arrow;
  • next chevron.

Some icons mirror in RTL; others (play, logos) may not.

Composition and data dependencies

Shared UI component should receive data/behavior, not fetch business resource:

Bad:

jsx
function UserAvatar({ userId }) {
  const query = useQuery(...);
}

for a design-system Avatar.

Better:

jsx
<Avatar
  src={user.avatarUrl}
  name={user.name}
/>

A feature-specific:

jsx
<UserAvatar userId={...} />

may wrap data loading if that is a deliberate feature component.

Name boundaries clearly.

Compound Context performance

Compound components often use Context:

jsx
<TabsContext value={...}>

If context contains rapidly changing large state, all consumers may rerender.

For complex component libraries, consider split contexts or external-store patterns.

Do not prematurely optimize small component sets.

Escape hatches in component APIs

Some components need refs:

jsx
<TextField ref={fieldRef} />

Modern React 19 supports ref as prop.

Expose imperative APIs only where declarative props cannot express needed behavior.

Error and loading slots

Reusable data panels can accept presentation slots:

jsx
<DataPanel
  loading={<PanelSkeleton />}
  error={(error) => <PanelError error={error} />}
>
  ...
</DataPanel>

But avoid building a generic "handles every async thing" component that duplicates TanStack Query/Suspense semantics.

Failure clinic

div with onClick styled as button

Semantics/keyboard broken.

Shared UI fetches feature API

Dependency inversion broken.

Every component accepts className and arbitrary props without policy

Can be useful, but design-system API becomes uncontrolled. Decide extension strategy.

Hard-coded English width

Translated UI overflows.

Animation ignores focus

Focus lands on unmounted/hidden nodes.

Exercises

  1. Redesign an invalid-state-heavy modal API.
  2. Build accessible Tabs compound API.
  3. Compare one headless library primitive against WAI-ARIA APG.
  4. Theme with CSS custom properties.
  5. Add locale-aware currency/date.
  6. Test layout in RTL and with 2× text.
  7. Audit an exit animation for focus/reduced motion.
  8. Draw dependency boundary between design system and feature API.

Mastery check

Explain:

  • invalid-state API design;
  • headless behavior;
  • styling-system trade-offs;
  • design tokens;
  • motion lifecycle;
  • i18n/RTL concerns;
  • data dependency direction.

Production case study: designing a reusable DataTable boundary

A DataTable can easily become a 60-prop monster.

Avoid mixing:

text
query fetching
pagination API
row selection
column rendering
permissions
CSV export
modal state
styling

into one universal component.

A better split:

text
OrdersTableFeature
├─ owns Query + URL + permissions
└─ composes
   DataTable
   ├─ columns
   ├─ rows
   ├─ selection callbacks
   └─ presentational states

Generic component:

jsx
<DataTable
  columns={columns}
  rows={orders}
  getRowKey={(order) => order.id}
  selectedKeys={selectedIds}
  onSelectionChange={setSelectedIds}
/>

Feature owns:

text
where rows came from
what selected orders mean
whether export is allowed
which route opens

This keeps the design-system component reusable without making it a hidden application framework.


Additional depth: React ecosystem choices without turning the course into library memorization

The React roadmap includes many optional ecosystem branches.

You should recognize categories and evaluate them.

Component systems

Examples in the ecosystem include:

text
MUI
Chakra UI
Mantine
Ant Design

They provide styled components/design systems.

Headless primitives

Examples:

text
Radix
React Aria
Headless UI
Ark UI

They emphasize behavior/accessibility primitives.

Animation

Examples:

text
Motion
GSAP
React Spring

Choose based on interaction complexity and bundle/runtime needs.

Frameworks

Examples:

text
Next.js
React Router Framework Mode
Astro with React islands

Frameworks add rendering/routing/server/deployment architecture.

Mobile

React Native uses React's component/state mental model but different host platform and component primitives.

Knowing React DOM does not mean knowing React Native layout, native modules, navigation, performance, or platform UX.

GraphQL

Apollo/urql and other clients may own GraphQL server cache.

If a project standardizes TanStack Query for REST, do not add Apollo merely because GraphQL exists. GraphQL architecture can justify a GraphQL-aware client.

The course teaches categories and decision criteria; specialist libraries should become separate advanced modules when the project actually uses them.