Unit 2: Component Design and Styling

INT252 — Web App Development With Reactjs 10 min read

I. Orientation — Building Maintainable React Interfaces

React organizes a user interface as a hierarchy of declarative components: JavaScript functions that describe UI from current props and state. Effective component design separates responsibilities, encourages composition, and combines accessible HTML with predictable styling.

  • Declarative model: A component describes what should appear for given data rather than manually changing individual DOM nodes.
  • Component hierarchy: Applications form trees in which parents render children and pass data downward through props.
  • One-way data flow: Data normally moves from parent to child; events communicate user actions upward through callback props.
  • Purity convention: Rendering should not mutate props, state, or external values; the same inputs should produce the same JSX.
  • Separation of concerns: Components may separate data coordination, interaction logic, visual presentation, and reusable behavior.
  • State principle: Keep state minimal and place it in the nearest common owner of every component that needs it.
  • Styling principle: Styling should be consistent, responsive, reusable, and independent of accidental markup details.
  • Accessibility principle: Prefer semantic HTML and native controls before adding ARIA attributes or custom keyboard behavior.

II. Component Construction — Reusable UI Building Blocks

Component construction turns an interface into small units with explicit inputs, focused responsibilities, and composable output.

A. Functional components

Functional components are JavaScript functions that receive props and return renderable JSX.

  • Definition: Component names begin with an uppercase letter so JSX distinguishes <Greeting /> from an HTML element such as <section>.
  • Input and output: Props enter as a function argument, while the return value may be JSX, a fragment, a string, a number, null, or another renderable value.
  • Hooks: Functions such as useState add React features without classes; hooks must be called unconditionally at the component’s top level.
  • Purity: Network requests, subscriptions, and DOM synchronization are side effects and belong in event handlers or effects, not directly in rendering.
JSX
function Greeting({ name }) {
  return <h2>Hello, {name}</h2>;
}

Here, name is an input prop and <h2> is the component’s rendered output.

B. Props-driven component reusability

Props make one component reusable by allowing its content, appearance, and behavior to vary through explicit inputs.

  • Read-only inputs: A child must not assign to or mutate its props; the parent remains the owner of supplied values.
  • Destructuring: function Avatar({ src, alt, size = 48 }) documents expected inputs and supplies a default size.
  • Behavior props: Callback props such as onSave allow a reusable component to report events without knowing the parent’s business logic.
  • Stable API: Prefer meaningful props such as variant="danger" over implementation-specific props such as redBackground.
  • Example: <Button variant="primary" onClick={submit}>Save</Button> combines configuration, behavior, and nested content.

C. Component composition patterns

Composition builds complex interfaces by nesting components rather than creating large components or deep inheritance structures.

  • Children composition: A wrapper receives nested JSX through the special children prop, as in <Card><Profile /></Card>.
  • Named slots: Props such as header, footer, or actions provide multiple insertion points when children alone is ambiguous.
  • Specialization: A specific component can configure a general one; for example, DeleteButton may render a reusable Button with a destructive variant.
  • Render props: A function prop can delegate rendering, although hooks and ordinary composition are often simpler.
  • Trade-off: Excessive nesting can obscure data flow, so each compositional layer should provide a clear structural or behavioral benefit.

D. Reusable component abstractions

A reusable abstraction captures a stable pattern while leaving changing data and behavior configurable.

  • Good abstraction: A Modal may own focus management and dialog structure while receiving its title, body, and close callback.
  • Avoid duplication: Repeated markup, class combinations, or interaction rules can justify extraction into a shared component.
  • Avoid premature generalization: Two visually similar elements do not require one component if their semantics or future behavior differ.
  • Controlled API: Expose only necessary variation through props; too many Boolean props such as small, blue, and rounded create conflicting combinations.
  • Escape hatch: A restrained className or children prop can support extension without embedding every possible option in the abstraction.

III. Rendering and Data Ownership — Coordinating Component Hierarchies

React components remain predictable when rendering branches are explicit, shared state has one owner, and responsibilities follow clear boundaries.

A. Conditional rendering strategies

Conditional rendering selects JSX according to state or props by using ordinary JavaScript expressions.

  • Early return: if (loading) return <Spinner />; isolates a complete loading branch before the main interface.
  • Ternary operator: {isLoggedIn ? <Dashboard /> : <Login />} suits an either-or choice because both outcomes are visible together.
  • Logical AND: {error && <Alert>{error}</Alert>} renders an element only when error is truthy; numeric zero requires care because 0 && ... renders 0.
  • Null result: Returning null intentionally renders nothing, although the component still participates in React’s lifecycle.
  • Readability: Move complex conditions into named variables such as const canEdit = owner && !archived; rather than nesting several operators in JSX.

B. Lifting state up in component hierarchies

Lifting state up moves shared state to the nearest common ancestor so sibling components use one authoritative value.

  • Single source of truth: If SearchInput changes a query and Results displays matching items, their parent should usually own query.
  • Downward data: The parent passes the current value to children through props.
  • Upward events: The parent passes a setter or domain callback, such as onQueryChange, for children to call.
  • Controlled component: The child displays a value supplied by its parent instead of maintaining a conflicting copy.
JSX
function SearchPage() {
  const [query, setQuery] = useState("");
  return <>
    <SearchInput value={query} onChange={setQuery} />
    <Results query={query} />
  </>;
}
  • Cost: Lifting state too high causes unnecessary coupling and broad re-renders; keep private state local when no sibling needs it.

C. Defining component responsibility boundaries

A component boundary is effective when the component has one coherent reason to change and a clear public interface.

  • Presentation responsibility: A ProductCard can focus on semantic markup and display props without fetching the entire catalogue.
  • Coordination responsibility: A page component may fetch records, handle errors, and distribute data to presentational children.
  • Interaction responsibility: A form can own input state and validation while delegating generic controls to TextField and Button.
  • Extraction signals: Repetition, difficult testing, deeply nested JSX, or a clearly named sub-interface can justify a new component.
  • Boundary warning: Components that mix unrelated fetching, navigation, formatting, and layout become difficult to reuse and modify.

IV. Styling Systems — From React CSS Choices to Tailwind Utilities

React does not prescribe a styling system; teams select approaches that balance scope, consistency, runtime cost, and developer experience.

A. Styling approaches in React applications

React supports several styling approaches, each with different scoping and maintenance characteristics.

  • Global CSS: Imported stylesheets suit resets and design tokens, but broad selectors can cause naming collisions.
  • CSS Modules: Files such as Card.module.css generate locally scoped class names and preserve standard CSS features.
  • Inline styles: style={{ color: "red" }} supports dynamic values but lacks ordinary pseudo-classes and media queries.
  • CSS-in-JS: Libraries can colocate dynamic styles with components, though they may add runtime or tooling costs.
  • Utility classes: Systems such as Tailwind apply small classes directly in JSX and reduce the need to invent selector names.
  • Class prop: React uses className, not class, because JSX properties follow DOM property conventions.

B. Tailwind CSS fundamentals

Tailwind CSS provides predefined utility classes that generate styles through a configured build process.

  • Utility mapping: p-4, font-semibold, and rounded-lg represent padding, font weight, and border radius.
  • Design scale: Spacing and color classes use a shared theme, helping mt-4 mean the same spacing step throughout the application.
  • State variants: Prefixes such as hover:, focus:, and disabled: apply utilities under defined conditions.
  • Build detection: Tailwind scans source files for class names and emits the required CSS; dynamically constructed fragments such as `text-${color}-500` may not be detected.
  • React usage: Conditional classes can be selected as complete strings: active ? "bg-blue-600" : "bg-gray-200".

C. Utility-first styling methodology

Utility-first styling constructs designs by combining narrowly focused classes directly on elements.

  1. Traditional semantic CSS: A class such as .profile-card hides several declarations in a stylesheet.
  2. Utility-first CSS: className="rounded-lg bg-white p-6 shadow" makes the applied design visible beside the markup.
  • Advantages: Utilities limit selector conflicts, encourage consistent scales, and make deletion safe because styles are tied closely to elements.
  • Costs: Long class strings can reduce readability and repeated combinations can create duplication.
  • Discipline: Arrange utilities consistently and extract a component when a repeated class set represents a genuine UI concept.
  • Separation: Utility-first design does not eliminate abstraction; it relocates many low-level styling decisions from custom selectors to standardized classes.

D. Responsive design using Tailwind breakpoints

Tailwind uses mobile-first breakpoint variants to apply utilities from a minimum viewport width upward.

  • Base styles: Unprefixed utilities apply to all viewport sizes and should describe the smallest layout first.
  • Breakpoint override: md:grid-cols-2 takes effect at the configured md width and continues above it unless overridden later.
  • Common prefixes: Default installations commonly provide sm, md, lg, xl, and 2xl, although projects may customize their exact widths.
  • Example: className="flex flex-col gap-4 md:flex-row" stacks items initially and changes to a row at md.
  • Design principle: Select breakpoints where content needs layout change, not merely for named device categories.

E. Reusable UI styling patterns

Reusable styling patterns encode consistent visual states while preserving semantic component APIs.

  • Variants: A Button can map primary, secondary, and danger props to complete, approved utility strings.
  • Class composition: Helpers such as clsx combine base, conditional, and caller-provided classes without manual spacing errors.
  • Design tokens: Theme colors, spacing, typography, and radii ensure repeated components follow one visual system.
  • State coverage: Shared controls should define hover, focus-visible, disabled, loading, and error appearances rather than only a default state.
  • Conflict control: Keep variant maps explicit so mutually incompatible utilities are not applied accidentally.
  • Abstraction test: Extract repeated styling when it represents a stable concept such as Badge or Alert, not merely because two elements share p-4.

V. Inclusive Markup — Accessible and Semantic Interfaces

Accessible React interfaces use correct HTML foundations, perceivable content, keyboard operation, and programmatic relationships.

A. Accessibility fundamentals

Accessibility enables people using keyboards, screen readers, magnification, voice control, or other assistive technologies to operate the interface.

  • Keyboard access: Interactive controls must be reachable and usable without a pointer; native <button> elements already support focus and Enter/Space activation.
  • Visible focus: Tailwind classes such as focus-visible:ring-2 may enhance focus indication, but focus outlines should not be removed without replacement.
  • Accessible names: Inputs require associated <label> elements, while icon-only buttons need a name such as aria-label="Close".
  • Image alternatives: Meaningful images need informative alt text; decorative images generally use alt="".
  • Color and status: Do not communicate errors only through red color; include text and, where appropriate, aria-describedby.
  • ARIA rule: Use ARIA to supplement missing semantics, not to replace suitable native HTML behavior.

B. Semantic HTML practices in React

Semantic HTML communicates document structure and control meaning to browsers and assistive technologies.

  • Landmarks: Use <header>, <nav>, <main>, <aside>, and <footer> according to their roles rather than wrapping everything in <div>.
  • Heading order: Use headings to express hierarchy—typically an <h1> followed by logically nested <h2> and <h3> sections.
  • Native controls: Use <button> for actions and <a href="..."> for navigation; a clickable <div> lacks equivalent keyboard and semantic behavior.
  • Lists and tables: Repeated list items belong in <ul> or <ol>; tabular relationships require <table>, headings, rows, and cells.
  • Forms: Connect labels with controls using htmlFor and id, because React uses htmlFor in JSX.
  • Preserved semantics: Styling a semantic element with Tailwind—such as <button className="...">—changes presentation without discarding its built-in accessibility.