Reusable Components and Visual Consistency

Capítulo 3

Estimated reading time: 13 minutes

+ Exercise

Why Reusable Components Matter

Reusable components are self-contained UI pieces (such as buttons, list rows, cards, input fields, and dialogs) designed once and used many times across an app. They package structure, styling, and behavior into a single unit so screens can be assembled consistently and efficiently. Visual consistency is the outcome: users see the same patterns for the same actions, which reduces cognitive load and makes the interface feel trustworthy and coherent.

In practice, reusable components act as the “source of truth” for how common UI elements look and behave. Instead of each screen implementing its own version of a primary button, you define one primary button component and reuse it everywhere. When you later adjust the corner radius, typography, or pressed state, the change propagates across the app with minimal effort.

Benefits you can measure

  • Speed of development: screens become composition work rather than repeated styling.
  • Fewer bugs: interaction states (disabled/loading/error) are implemented once and tested once.
  • Consistent accessibility: focus order, touch targets, labels, and contrast can be standardized.
  • Scalable design: new features can adopt existing patterns instead of inventing new ones.
  • Easier maintenance: global updates (brand refresh, theme changes) are safer and faster.

Component Thinking: From Screens to a Design System

A common mistake is to start by building screens and only later extracting components. That approach often produces near-duplicates: “PrimaryButton” on one screen, “MainButton” on another, each with slightly different padding and colors. Component thinking flips the process: identify repeated patterns early, define a component API, and then compose screens from those components.

Atomic levels (practical interpretation)

  • Tokens: named values like colors, typography styles, corner radii, elevation, and motion durations.
  • Primitives: small building blocks like Icon, Text, Spacer, Divider.
  • Components: Button, TextField, Card, Chip, ListItem.
  • Compositions: SearchBar with filters, ProductCard with price and rating, SettingsRow with a toggle.
  • Templates: screen-level arrangements that define regions and behavior patterns (e.g., “List + Details”).

This chapter focuses on the component and composition layers, and how they enforce visual consistency without repeating earlier layout fundamentals.

Visual Consistency: What Must Stay the Same

Consistency is not about making everything identical; it is about making similar things behave and appear similarly. Users build expectations quickly: a primary action should look like a primary action everywhere, destructive actions should be clearly signaled, and interactive elements should respond predictably.

Continue in our app.
  • Listen to the audio with the screen off.
  • Earn a certificate upon completion.
  • Over 5000 courses for you to explore!
Or continue reading below...
Download App

Download the app

Consistency checklist for components

  • Color roles: use semantic roles (primary, surface, error, success) rather than hard-coded hex values in components.
  • Typography roles: use named text styles (Title, Body, Caption) rather than ad-hoc font sizes.
  • Shape: consistent corner radius and outline thickness across comparable components.
  • Iconography: consistent stroke weight, filled/outlined style, and icon sizes.
  • States: default, pressed, focused, disabled, loading, selected, error.
  • Motion: consistent durations and easing for similar transitions (e.g., button press feedback).
  • Feedback: consistent placement and style for errors, helper text, and success confirmations.

When these rules are embedded into reusable components, screens naturally inherit consistency.

Step-by-Step: Designing a Reusable Component Library

The most effective component libraries start small and grow intentionally. The steps below apply whether you are using native UI toolkits, cross-platform frameworks, or a custom rendering layer.

Step 1: Inventory repeated UI patterns

Collect screenshots of your current screens (or wireframes) and mark repeated elements. Look for:

  • Primary/secondary buttons
  • Form fields (text, password, search)
  • List rows (with leading icon, trailing chevron, subtitle)
  • Cards (image + title + metadata)
  • Badges/chips/tags
  • Empty states and error banners

Group them by purpose, not by appearance. For example, “call-to-action button” is a purpose; it might have multiple variants (primary, secondary, destructive) but should share a base component.

Step 2: Define component responsibilities and boundaries

A reusable component should have a clear job and avoid knowing too much about the screen it lives on. A good boundary is: the component owns its internal styling and interaction states, while the screen owns layout placement and data flow.

  • Component owns: colors, typography roles, padding inside the component, states (disabled/loading), internal alignment, accessibility labels defaults.
  • Screen owns: where the component is placed, what data it displays, navigation actions, and spacing between components.

This separation prevents “leaky” components that are hard to reuse because they assume a specific context.

Step 3: Create a stable API (props/parameters)

Component APIs should be small, semantic, and hard to misuse. Prefer parameters like variant, tone, and size over raw values like backgroundColor unless you are building a low-level primitive.

Example: a button API that encourages consistency.

// Pseudocode API (framework-agnostic)  Component Button(    label: String,    onPress: () -> Void,    variant: ButtonVariant = .primary,   // primary | secondary | tertiary    tone: ButtonTone = .neutral,         // neutral | destructive    size: ButtonSize = .md,              // sm | md | lg    leadingIcon: Icon? = null,    isLoading: Bool = false,    isDisabled: Bool = false  )

With this API, designers and developers can express intent (“destructive secondary button”) without inventing new styles.

Step 4: Implement states as first-class behavior

Visual consistency often breaks in states: one screen dims disabled buttons, another changes the label color, another removes the shadow. Implement states inside the component so they are uniform everywhere.

  • Disabled: reduce emphasis but keep legibility; ensure it is still recognizable as a button.
  • Loading: reserve space so the button does not resize; disable presses while loading.
  • Pressed/focused: provide immediate feedback; ensure focus rings are consistent for keyboard/TV use if applicable.
  • Error: for inputs, show error text and error border consistently.

For example, a loading button should not cause layout jumps. A common approach is to keep the label visible but faded and overlay a spinner, or replace the label while keeping width fixed.

Step 5: Document usage rules with examples

Documentation is part of the component. Without it, teams will misuse components and create one-off variants. For each component, document:

  • When to use it (and when not to)
  • Variants and tones
  • Do’s and don’ts (e.g., “don’t place two primary buttons side by side”)
  • Accessibility notes (labels, hints, error messaging)
  • Examples of correct composition

Keep examples practical: show how a component behaves in a form, in a list, and in a dialog.

Practical Example 1: Building a Consistent Button Set

Buttons are among the most visible consistency signals. A robust button component typically includes variants (visual hierarchy), tones (semantic meaning), and sizes (density).

Step-by-step: define button variants

  • Primary: highest emphasis for the main action on a screen.
  • Secondary: alternative action with lower emphasis.
  • Tertiary/Text: low emphasis, often for inline actions.
  • Destructive tone: indicates irreversible or risky actions (e.g., Delete).

Then map each variant to tokenized styles. Avoid hard-coded values in the component; instead reference tokens like color.primary and radius.md.

// Pseudocode style mapping  styles.primary = {    background: tokens.color.primary,    foreground: tokens.color.onPrimary,    border: none  }  styles.secondary = {    background: tokens.color.surface,    foreground: tokens.color.primary,    border: tokens.border.subtle  }  styles.tertiary = {    background: transparent,    foreground: tokens.color.primary,    border: none  }  styles.destructivePrimary = {    background: tokens.color.error,    foreground: tokens.color.onError  }

Step-by-step: ensure consistent touch feedback

Implement a uniform pressed state. For example:

  • Primary: darken background slightly on press.
  • Secondary: add subtle surface tint and strengthen border.
  • Tertiary: add a faint background highlight behind text.

Keep the same animation duration for all button presses so the app feels cohesive.

Step-by-step: handle icon + label alignment

Buttons with icons often drift into inconsistency: different icon sizes, uneven spacing, or misaligned baselines. Standardize:

  • Icon size per button size (e.g., 16/20/24)
  • Gap between icon and label (tokenized)
  • Whether icons are allowed on both sides (leading/trailing)

By enforcing these rules in the component, you avoid per-screen tweaks.

Practical Example 2: A Reusable Form Field with Validation

Text inputs are a common source of inconsistency because they combine multiple elements: label, input box, placeholder, helper text, error text, and optional icons. A reusable component should unify these pieces.

Define a single “Field” component that composes subparts

Instead of separate components for label and error message scattered across screens, create a Field component that renders:

  • Label (optional)
  • Input container (with border/background)
  • Leading/trailing icon slots (optional)
  • Helper text or error text (mutually exclusive)
// Pseudocode API  Component TextField(    label: String? = null,    value: String,    onChange: (String) -> Void,    placeholder: String? = null,    helperText: String? = null,    errorText: String? = null,    leadingIcon: Icon? = null,    trailingAction: (() -> Void)? = null, // e.g., clear, show password    keyboardType: KeyboardType = .default,    isDisabled: Bool = false  )

Step-by-step: implement validation visuals consistently

  • If errorText is present: show error border color, show error text, and optionally an error icon.
  • If focused and no error: show focus border color.
  • If disabled: reduce contrast and prevent interaction.

Make sure the component reserves space for helper/error text to avoid layout jumps when an error appears. One approach is to always render a text line below the field and switch its content and color.

Step-by-step: standardize “clear” and “show password” actions

Trailing actions are often implemented inconsistently. Provide a standard slot and rules:

  • Icon size and hit area are consistent.
  • Action is only visible when relevant (e.g., clear appears when value is not empty).
  • Accessibility label is required (e.g., “Clear text”).

By making trailing actions part of the component API, you avoid custom per-screen icon buttons that look slightly different.

Component Composition Patterns That Preserve Consistency

Once you have core components, you will build larger compositions. Consistency depends on using the same compositions for the same tasks.

List items: one component, many configurations

Lists appear everywhere: settings, search results, messages, product catalogs. Create a single ListItem that supports common slots:

  • Leading: icon/avatar/thumbnail
  • Title and optional subtitle
  • Trailing: chevron, value, toggle, badge
  • Optional divider
// Pseudocode API  Component ListItem(    title: String,    subtitle: String? = null,    leading: ListLeading? = null,    trailing: ListTrailing? = .chevron,    onPress: (() -> Void)? = null,    isEnabled: Bool = true  )

This prevents a “settings row” from being built differently than a “profile row” when they are functionally the same pattern.

Cards: define elevation, padding, and content rules

Cards often drift: some have heavy shadows, some have none; some have tight padding, others are airy. Standardize card types:

  • Surface card: subtle border, minimal elevation.
  • Elevated card: stronger elevation for emphasis.
  • Interactive card: includes pressed state and focus style.

Then provide a Card component with a consistent container style and allow content via slots. This keeps the “frame” consistent while allowing different content inside.

Tokens: The Foundation of Visual Consistency

Reusable components stay consistent when they are built on design tokens: named, centralized values for color, typography, shape, and motion. Tokens allow you to change the look of the entire app (or support multiple themes) without rewriting component logic.

Use semantic tokens, not raw values

Prefer tokens like color.textPrimary and color.surface over #111111 and #FFFFFF. Semantic tokens encode meaning and adapt better to dark mode, high contrast modes, and brand changes.

// Example token categories (conceptual)  tokens.color = {    primary, onPrimary, surface, onSurface, error, onError, outline, mutedText  }  tokens.type = {    title, body, caption, button  }  tokens.shape = {    radiusSm, radiusMd, radiusLg  }  tokens.motion = {    fast, normal, slow  }

Components should reference these tokens exclusively for styling, except in rare cases where a component is explicitly a low-level primitive.

Preventing “Variant Explosion”

As apps grow, teams often add variants for every new request: “PrimaryButtonRounded”, “PrimaryButtonCompact”, “PrimaryButtonForOnboarding”. This creates inconsistency and maintenance burden. Instead, control variation through a small set of orthogonal options.

Practical rules to keep variants under control

  • Prefer composition over new variants: wrap a standard button in a screen-level container if you need different placement, not a new button style.
  • Add a variant only when it represents a repeated pattern: if it appears in multiple places and has a clear semantic meaning.
  • Use size and tone parameters: many “new variants” are actually size or tone differences.
  • Deprecate old variants: keep a migration path and remove unused styles.

A helpful test: if you cannot describe the variant in one sentence that explains its purpose, it is probably a one-off and should not become a library variant.

Accessibility as a Consistency Requirement

Accessibility is not an add-on; it is part of consistent component behavior. When accessibility is handled at the component level, every screen benefits automatically.

Component-level accessibility practices

  • Minimum touch target: ensure interactive components meet platform guidelines by default.
  • Readable labels: require a label for icon-only buttons; provide sensible defaults for common patterns.
  • Focus and keyboard navigation: define consistent focus outlines and focus order within composite components.
  • Error messaging: ensure errors are announced and visually tied to the field.
  • Dynamic text sizing: ensure components expand gracefully when text size increases.

When these behaviors are embedded into reusable components, you reduce the risk of some screens being accessible and others not.

Testing Reusable Components to Protect Consistency

Consistency can degrade over time as multiple contributors modify components. Testing helps prevent regressions.

Practical testing approaches

  • Visual regression snapshots: capture component states (default, pressed, disabled, loading, error) and compare changes.
  • Interaction tests: verify that disabled components do not respond, loading blocks repeated taps, and focus behavior works.
  • Accessibility checks: ensure labels exist, contrast is acceptable, and dynamic text does not clip.
  • Theme tests: validate components in light/dark (and high contrast if supported).

Even a small set of snapshot tests for core components (Button, TextField, ListItem, Card) can prevent subtle inconsistencies from spreading.

Workflow: How Designers and Developers Stay Aligned

Reusable components work best when design and engineering share the same component vocabulary. The goal is to avoid “design says one thing, code implements another” drift.

Practical alignment steps

  • Name components consistently: the same names in design files and code (e.g., “Button/Primary”).
  • Define component specs: include states, spacing inside the component, and behavior rules.
  • Review changes centrally: treat component changes as high-impact; review with both design and engineering.
  • Provide a component gallery screen: a simple in-app page that shows all components and states for quick review.

A component gallery is especially useful in mobile apps because it allows quick verification on real devices, different screen sizes, and different OS versions.

Common Pitfalls and How to Avoid Them

Over-generalizing too early

If a component API tries to support every possible layout, it becomes complex and hard to use. Start with the most common use cases, then extend carefully when a new pattern repeats.

Allowing arbitrary styling overrides

Letting screens pass raw colors, font sizes, or paddings into high-level components often breaks consistency. Prefer semantic parameters and restrict overrides to low-level primitives.

Duplicating components instead of extending

When a new feature needs a slight tweak, teams sometimes copy an existing component and modify it. This creates divergence. Instead, add a parameter (if it is broadly useful) or create a composition that wraps the base component.

Ignoring empty, loading, and error states

Many inconsistencies appear when data is missing or slow. Build reusable patterns for skeleton loading, empty states, and error banners so every screen handles them consistently.

Now answer the exercise about the content:

When defining boundaries for a reusable UI component, which responsibility should belong to the component rather than the screen?

You are right! Congratulations, now go to the next page

You missed! Try again.

A reusable component should own its internal styling and states to stay consistent everywhere, while the screen controls placement, spacing, data flow, and navigation.

Next chapter

Responsive Design Principles for Multiple Screen Sizes

Arrow Right Icon
Free Ebook cover Mobile App UI Fundamentals: Layout, Navigation, and Responsiveness
19%

Mobile App UI Fundamentals: Layout, Navigation, and Responsiveness

New course

16 pages

Download the app to earn free Certification and listen to the courses in the background, even with the screen off.