Real UI Components
Real UI components in React: reusable Button, Card, Badge, Alert, Input, Modal, and Navbar with clear APIs, prop forwarding, composition, and accessibility.
Real UI Components
Real UI Components are reusable React components designed to represent actual interface elements found in modern applications.
So far, we have learned how to create components, pass props, handle events, render lists, conditionally render UI, and style with CSS, CSS Modules, and Tailwind. Now we combine those concepts.
Instead of isolated examples, build real application components:
Navbar Button Card Modal Alert BadgeInput Dropdown Sidebar Table Pagination
The goal is to create reusable components with a clear API, predictable behavior, and consistent styling.
Learning Objectives
- What real UI components are and why reuse matters
- Design clear component APIs (variants, sizes, children)
- Prop forwarding and native HTML attributes
- Build Button, Card, Badge, Alert, Input, Modal, Navbar, Spinner
- UI vs page vs feature components
- Accessibility, semantic HTML, and composition
What Is a Real UI Component?
A real UI component represents a meaningful piece of an application's interface.
<Button>Save</Button><Cardtitle="React Fundamentals"description="Learn React step by step."/>
A real UI component usually combines:
Structure + Styling + Behavior + Props + Accessibility + Reusability
Why Build Reusable UI Components?
Without reusable components, 30 buttons across pages drift apart — different padding, colors, radius, hover, and disabled behavior. Centralize instead:
<Button variant="primary">Save</Button>
30 usages → Button → Consistent UI
Component API
<Buttonvariant="primary"size="large"disabled={false}>Save</Button>
Props like variant, size, disabled, and children define how consumers interact. A good API is predictable, easy to understand, flexible, and difficult to misuse.
Building a Real Button
function Button({children,variant = "primary",size = "medium",disabled = false}) {const variants = {primary: "bg-blue-600 text-white hover:bg-blue-700",secondary: "bg-gray-200 text-gray-900 hover:bg-gray-300",danger: "bg-red-600 text-white hover:bg-red-700"};const sizes = {small: "px-3 py-1.5 text-sm",medium: "px-4 py-2",large: "px-6 py-3 text-lg"};const className = ["rounded-md font-medium transition",variants[variant],sizes[size],disabled && "opacity-50 cursor-not-allowed"].filter(Boolean).join(" ");return (<button className={className} disabled={disabled}>{children}</button>);}<Button>Save</Button><Button variant="secondary">Cancel</Button><Button variant="danger">Delete</Button><Button size="large">Continue</Button>
Understanding children
children is the content between opening and closing tags:
function Button({ children }) {return <button>{children}</button>;}<Button>Save</Button>// children = "Save"
Accepting Native HTML Props
Collect remaining props with the rest operator:
function Button({children,variant = "primary",size = "medium",...props}) {return (<button className="..." {...props}>{children}</button>);}<Buttontype="submit"aria-label="Save changes"disabled={isSaving}>Save</Button>
Why Prop Forwarding Matters
Consumer↓<Button onClick={...} disabled={...}>↓<button onClick={...} disabled={...}>
This pattern is called prop forwarding — appropriate native props reach the underlying element.
Building a Card Component
function Card({ title, description, children }) {return (<article className="rounded-xl border border-gray-200 bg-white p-6 shadow-sm"><h2 className="text-xl font-semibold text-gray-900">{title}</h2><p className="mt-2 text-gray-600">{description}</p><div className="mt-4">{children}</div></article>);}<Cardtitle="React Fundamentals"description="Learn the fundamentals of React."><Button>Start Learning</Button></Card>
Composition: the card does not need to know what its child content is.
Component Composition
Prefer composing small components over giant ones like CourseCardWithButtonAndBadgeAndImage:
<Card><Badge>Beginner</Badge><h2>React Fundamentals</h2><p>Learn React step by step.</p><Button>Start Learning</Button></Card>
Building a Badge
function Badge({ children, variant = "default" }) {const variants = {default: "bg-gray-100 text-gray-800",success: "bg-green-100 text-green-800",warning: "bg-yellow-100 text-yellow-800",error: "bg-red-100 text-red-800"};return (<span className={`rounded-full px-3 py-1 text-sm ${variants[variant]}`}>{children}</span>);}<Badge>Draft</Badge><Badge variant="success">Published</Badge><Badge variant="warning">Pending</Badge><Badge variant="error">Failed</Badge>
Building an Alert
function Alert({ type = "info", title, children }) {const styles = {info: "bg-blue-50 text-blue-800",success: "bg-green-50 text-green-800",warning: "bg-yellow-50 text-yellow-800",error: "bg-red-50 text-red-800"};return (<div role="alert" className={`rounded-lg p-4 ${styles[type]}`}>{title && <h3 className="font-semibold">{title}</h3>}<div className="mt-1">{children}</div></div>);}<Alert type="success" title="Success">Your profile has been updated.</Alert>
role="alert" gives semantic meaning for assistive technologies.
Building an Input Component
function Input({ label, error, ...props }) {return (<div>{label && (<label className="mb-1 block font-medium">{label}</label>)}<inputclassName="w-full rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:ring-2"{...props}/>{error && (<p className="mt-1 text-sm text-red-600">{error}</p>)}</div>);}<Input label="Email" type="email" placeholder="Enter your email" /><Input label="Email" type="email" error="Please enter a valid email address." />
Building a Modal
function Modal({ open, title, children, onClose }) {if (!open) return null;return (<div className="fixed inset-0 flex items-center justify-center bg-black/50"><div className="w-full max-w-md rounded-xl bg-white p-6"><div className="flex items-center justify-between"><h2 className="text-xl font-semibold">{title}</h2><button onClick={onClose} aria-label="Close dialog">×</button></div><div className="mt-4">{children}</div></div></div>);}<Modalopen={isOpen}title="Delete Account"onClose={() => setIsOpen(false)}><p>Are you sure you want to delete your account?</p><Button variant="danger">Delete</Button></Modal>
Combines state, props, conditional rendering, events, children, and styling. Production modals also need dialog semantics, focus management, and keyboard handling.
Building a Navbar
function Navbar() {return (<nav className="border-b bg-white"><div className="mx-auto flex max-w-7xl items-center justify-between px-6 py-4"><a href="/" className="text-xl font-bold">MyApp</a><div className="flex gap-6"><a href="/">Home</a><a href="/courses">Courses</a><a href="/about">About</a></div></div></nav>);}
Later enhance with responsive nav, mobile menu, active links, auth state, and dropdowns — without changing every page that uses it.
Building a Loading Spinner
function Spinner() {return (<divclassName="h-6 w-6 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600"aria-label="Loading"role="status"/>);}{loading && <Spinner />}
Component Variants
Prefer one component with a variant prop over PrimaryButton / DangerButton:
<Button variant="primary">Save</Button><Button variant="danger">Delete</Button>
Component Sizes
<Button size="small">Small</Button><Button size="medium">Medium</Button><Button size="large">Large</Button>const sizes = {small: "...",medium: "...",large: "..."};
Boolean Props
<Button disabled={isSaving}>Save</Button><Modal open={isOpen}>...</Modal><Card featured>...</Card>
Useful for simple on/off features.
Avoid Too Many Props
Avoid APIs like:
<Button blue rounded large shadow bold uppercase border ...>
Prefer meaningful variants that communicate intent:
<Button variant="primary" size="large">Save</Button>
Component States
Button: Normal, Hover, Focus, Active, Disabled, LoadingInput: Empty, Focused, Filled, Invalid, Disabled, Read-only
<Button disabled={loading}>{loading ? "Saving..." : "Save"}</Button>
Real UI Component Architecture
src/components/├── Button/Button.jsx├── Card/Card.jsx├── Badge/Badge.jsx├── Input/Input.jsx├── Modal/Modal.jsx├── Alert/Alert.jsx└── Navbar/Navbar.jsx
Larger components can group tests and styles (or just JSX + tests when using Tailwind).
UI Components vs Page Components
Application → Pages → Feature Components → UI ComponentsCourseDetailsPage├── CourseHeader → Badge, Button├── CourseDescription└── LessonList → LessonItem
Button is reusable across pages; CourseDetailsPage is a whole screen.
UI Components vs Feature Components
UI Components
Generic: Button, Input, Modal, Badge, Card, Spinner
Feature Components
Domain-specific: CourseCard, CourseProgress, CheckoutForm, UserProfile, OrderSummary
Composition Over Giant Components
function Dashboard() {return (<><Navbar /><div className="flex"><Sidebar /><main><StatsCard /><ActivityList /><RecentOrders /></main></div></>);}
Accessibility Is Part of the Component
<button aria-label="Close" onClick={onClose}>×</button><label htmlFor="email">Email</label><input id="email" type="email" /><img src="/avatar.jpg" alt="John's profile" />
Build accessibility into reusable components so every usage inherits it.
Semantic HTML
Use button for actions, a for navigation, nav, main, article appropriately. Avoid <div onClick> when a semantic element already exists.
Real UI Component Mental Model
Component│┌─────────────┼─────────────┐▼ ▼ ▼API UI Behavior(props) (styles) (events)│ │ │└─────────────┼─────────────┘▼Reusable Component
Ask: What does it render? Which props? Which states? Which events? Is it accessible? Reusable? Is the API clear?
A Complete Example
function Badge({ children }) {return (<span className="rounded-full bg-blue-100 px-3 py-1 text-sm text-blue-700">{children}</span>);}function Button({ children, ...props }) {return (<buttonclassName="rounded-md bg-blue-600 px-4 py-2 font-medium text-white hover:bg-blue-700"{...props}>{children}</button>);}function CourseCard({ title, description, level, onStart }) {return (<article className="rounded-xl border bg-white p-6 shadow-sm"><Badge>{level}</Badge><h2 className="mt-3 text-xl font-bold">{title}</h2><p className="mt-2 text-gray-600">{description}</p><Button className="mt-5" onClick={onStart}>Start Learning</Button></article>);}<CourseCardtitle="React Fundamentals"description="Learn React from the ground up."level="Beginner"onStart={() => console.log("Course started")}/>
CourseCard → Badge + Heading + Description + Button
Common Mistakes
- Too large — split unrelated sections
- Every div a component — extract when there is real responsibility or reuse
- Too early — extract when a pattern emerges
- Too many props — prefer composition or splitting
- Hiding native HTML — forward type, disabled, onClick, aria-*
- Ignoring accessibility — keyboard and assistive tech matter
Best Practices
- Keep components focused
- Design clear APIs (
variant,size) - Support composition with
children - Forward appropriate native props
- Handle states: loading, disabled, error, empty, active, hover, focus
- Build accessibility into components
- Prefer composition over giant components
- Keep styling strategy consistent
Practice Exercises
- Button — primary/secondary/danger + small/medium/large
- Card — title, description, children
- Alert — info/success/warning/error + accessible
- Input — label, error, placeholder, type, value, onChange
- Modal — open, title, children, onClose, overlay, a11y
- Course Dashboard — compose Navbar, Sidebar, Card, Badge, Button
Interview Questions
1. What is a reusable UI component?
A React component that encapsulates meaningful UI, styling, and often behavior for reuse.
2. What is component composition?
Building larger interfaces by combining smaller components.
3. What is the children prop?
Content between a component's opening and closing tags.
4. What is prop forwarding?
<button {...props}>
5. What are component variants?
<Button variant="danger" />
6. When should something become a component?
When it has meaningful responsibility, reusable behavior, repeated structure, or improves organization.
7. UI vs feature component?
UI is generic (Button, Input); feature is domain-specific (CourseCard, CheckoutForm).
8. Why accessibility in reusable components?
Problems otherwise repeat everywhere the component is used.
9. Why not every div?
Excessive abstraction hurts readability — components should be meaningful units.
10. What is a good component API?
Predictable, focused, easy to use, flexible for real cases, hard to misuse.
Quick Cheat Sheet
| Concept | Pattern |
|---|---|
| Children | <Card>Content</Card> |
| Native props | ...props on underlying element |
| Variants | variant="danger" |
| Sizes | size="large" |
| Composition | Card + Badge + Button |
| Conditional | {loading && <Spinner />} |
| A11y | aria-label, labels, roles |
Key Takeaway
Real UI components are where React becomes real application development — think in building blocks (Button, Card, Modal) not only HTML tags.
Props → Component → UI + State + Events → Reusable UI
Create the right components: clear responsibilities, useful APIs, reusable behavior, consistent styling, and accessibility built in. Next: component libraries, design systems, forms, navigation, and application-level UI architecture.