Modern front-end frameworks (like React, Vue, Angular, and Svelte) help you build websites and web apps by breaking the interface into small, reusable parts called components. You also need a clear way to handle navigation between views (routing) and a project structure that stays clean as your codebase grows.
1) What is a Component?
A component is a self-contained piece of the UI that includes:
- Structure (what it renders)
- Behaviour (how it responds to user actions)
- Styling (how it looks)
Think of components as building blocks. Instead of writing one large page, you build many small pieces and compose them into screens.
Examples of common components
ButtonInputFieldNavbarFooterCardModalProductListLoginForm
Why components matter
Components improve:
- Reusability: build once, use many times
- Consistency: same UI patterns across the site
- Maintainability: changes are isolated
- Testability: smaller units are easier to test
2) Component Design: “Reusable vs Specific”
A useful way to design components is to separate them into:
A) Reusable (generic) UI components
These are not tied to one feature. They can be used anywhere.
Examples:
ButtonTextFieldAlertSpinnerCard
Characteristics:
- Accept configuration through props (inputs)
- Avoid hard-coded text and layout assumptions
- Are focused on one job (single responsibility)
B) Feature (domain) components
These are tied to a specific business feature.
Examples:
CheckoutSummaryUserProfilePanelProductFilterSidebar
Characteristics:
- Combine multiple generic components
- Contain feature-specific logic
- Often connected to state and API data
3) Props, Slots, and Composition (Building Flexible UI)
Different frameworks use different terms, but the idea is similar: pass data into a component and allow it to render flexible content.
Props (inputs)
Props allow the parent to control a child component.
Typical uses:
- button label:
label="Save" - visual style:
variant="primary" - disabled state:
disabled={true} - event callbacks:
onClick={handleSave}
Guideline: avoid components reading global state directly unless they are truly “smart”/container components. Keep most UI components “dumb” and configurable.
Composition
Instead of creating many variations of the same component, you can build a base component and compose it.
Example approach:
Cardas a layout containerCardHeader,CardBody,CardFooteras optional parts- Or allow the parent to pass inner content
Composition reduces duplication and keeps the UI system consistent.
4) Reusable UI Patterns You’ll Use Often
A) Buttons (variants and states)
A good button component should support:
- Variants: primary, secondary, danger, link
- States: default, hover, focus, disabled, loading
- Accessibility: correct element type and focus styling
Accessibility note: Use a real <button> for actions and an <a> for navigation. Don’t use a <div> that “acts like a button”.
B) Form inputs with validation
Reusable form controls typically include:
- Label
- Help text
- Error message
- Required indicator
- Accessible connection between label and input (
for/idor framework equivalent)
Accessibility note: ensure error messages are announced to screen readers (for example, using an ARIA live region where appropriate).
C) Lists and keys (stable identity)
When rendering lists of items, frameworks rely on keys to track items efficiently.
Guideline: use stable IDs (like a database ID), not array indexes, to avoid UI bugs when items are added/removed/reordered.
D) Modals, toasts, and overlays
These often look simple but require careful handling:
- Focus should move into the modal when it opens
- Focus should return to the triggering element when it closes
- Escape key should close (if appropriate)
- Background content should not be reachable by keyboard while modal is open
5) Smart vs Dumb Components (Container vs Presentational)
A common scalable approach:
Presentational components (“dumb”)
- Focus on UI only
- Receive data and callbacks via props
- Easy to reuse and test
Container components (“smart”)
- Fetch data from APIs
- Read/write state
- Handle side effects (loading, errors)
- Pass processed data down to presentational components
This separation prevents your UI components from becoming tightly coupled to your data layer.
6) Routing: Navigation in Single Page Applications (SPAs)
In an SPA, navigation is handled by a router. Instead of loading a new HTML page from the server each time, the app updates the view based on the URL.
What routing provides
- Map URLs to views/screens (routes)
- Support nested layouts (e.g., dashboard with side navigation)
- Enable route parameters (
/products/:id) - Manage “not found” pages (404)
- Control access (protected routes)
Key concepts
- Route: a URL pattern linked to a component/view
- Link: a router-aware navigation element (prevents full reload)
- Route parameters: variables in the URL (like an ID)
- Query string: filters/sorting (
?sort=price&category=shoes) - Nested routes: child routes rendered inside parent layouts
7) Route Structure for Real Projects
A clean route structure often mirrors features and layouts.
Example patterns you’ll see:
- Public pages:
/,/about,/pricing - Auth:
/login,/register,/forgot-password - App area:
/app/...or/dashboard/... - Resource pages:
/products,/products/:id,/products/:id/edit
Layout routes (shared UI)
Many frameworks support a route that renders a layout plus a nested outlet for child pages.
Examples of shared layout components:
MainLayout(header + footer)DashboardLayout(side nav + top bar)AuthLayout(simple centred form layout)
This prevents repeating navigation and layout markup across pages.
8) Protected Routes (Authentication and Authorisation)
Some pages should only be available to logged-in users (authentication) or to certain roles (authorisation).
Common approach:
- Use a route guard / wrapper that checks:
- Is the user logged in?
- Do they have the required role/permissions?
- If not:
- Redirect to login
- Or show an “Access denied” page
Good practice: don’t rely only on front-end route protection. The back-end must enforce permissions too. Front-end checks are mainly for user experience.
9) Routing and Data: Loading, Errors, and Empty States
When a route depends on API data (e.g., /products/:id):
- Show a loading state (spinner/skeleton)
- Handle error states (network errors, 404, server issues)
- Handle empty states (no data yet)
Design these states as reusable UI components:
LoadingSpinnerorSkeletonCardErrorMessageEmptyState
This keeps pages consistent and reduces duplicated logic.
10) Project Structure for Scalability and Maintainability
A framework gives you tools, but you still need a clear structure.
Typical folder groupings (conceptually)
- components: reusable UI (buttons, inputs, cards)
- features/pages: route-level screens and feature-specific components
- layouts: shared page structures
- routes: routing configuration
- services/api: API clients, request helpers, endpoints
- state: state management (stores, slices, reducers)
- utils: helpers (formatting, validation)
- styles: global styles, variables, themes
- assets: images, icons, fonts
- tests: unit/integration tests
Guideline: group by feature when the app grows. Small projects can group by type (components, utils, services), but large projects often scale better with feature-based structure.
11) Creating a Reusable UI System (Design Tokens and Consistency)
To avoid inconsistent styling (10 different blues, 6 button sizes), define:
- Design tokens: colours, spacing, font sizes, border radius
- Component variants: clear rules for primary/secondary actions
- Consistent spacing system: e.g., 4px or 8px scale
Whether you use CSS variables, a utility framework, or a component library, the goal is the same: consistent, maintainable UI.
12) Accessibility and Reusability (Important Together)
Reusable components must be accessible by default, otherwise you repeat accessibility problems everywhere.
Key checklist:
- Keyboard support (tab, enter, space, escape where relevant)
- Visible focus states
- Correct HTML semantics (buttons, links, headings)
- Labels for form inputs
- ARIA only when needed (don’t “ARIA over” bad HTML)
- Colour contrast meets WCAG guidelines
Tip: treat accessibility as part of the component definition, not a “later fix”.
13) Practical Task (Hands-on)
Build a small component set and connect it with routing:
-
Create reusable components:
Button(variants + disabled + loading)TextField(label + error)Card(simple container)Alert(success/error/info)
-
Create pages (route-level):
- Home page (
/) - Products list (
/products) - Product details (
/products/:id) - Not found page (
*or/404)
- Home page (
-
Add navigation:
Navbarwith router links- Active link styles (show the current page)
-
Add states:
- Loading and error UI when fetching product data
- Empty state if no products exist
Deliverable: a small SPA that demonstrates clean component design, working navigation, and consistent reusable UI.
Key Takeaways
- Components help you build UIs in small, reusable pieces.
- Reusable components should be flexible (props/composition) and accessible by default.
- Routing connects URLs to views without full page reloads.
- A good project structure and shared layouts keep your codebase scalable and easy to maintain.