Building Your First Component Tree: App, Header, Main, WelcomeCard, Footer
Write and connect multiple components across separate files using imports and exports, and render a small, real component tree using JSX/TSX.
A React application is really just a tree of components rendering other components. In this lesson you'll build a small but complete tree — an App component composed of a Header, a Main section containing a WelcomeCard, and a Footer — spread across multiple files, exactly the pattern every real React project follows at a larger scale.
JSX and TSX recap: components as functions that return markup-like syntax
JSX (JavaScript XML) is a syntax extension that lets you write HTML-like markup directly inside JavaScript/TypeScript code; TSX is simply the name for JSX used inside a .tsx (TypeScript) file, so that the TypeScript compiler knows to expect and type-check JSX syntax in that file. Every component you write is an ordinary function; what makes it a React component is that it returns JSX describing what should appear on screen.
Because JSX is just JavaScript under the hood, you can use full TypeScript inside it — variables, conditionals, function calls — anywhere you're not literally inside a markup tag. This is what makes components composable: a component can call other components inside its returned JSX exactly the way it would call any other function, just written as a tag, like <WelcomeCard />.
Imports and exports: connecting files together
Each component typically lives in its own file, and files share code through ES module import/export statements. A component file usually has a single export default — the component itself — so that other files can import it with any local name they choose: import WelcomeCard from "./WelcomeCard";. Named exports (export function Header() {} imported as import { Header } from "./Header";) are used instead when a file exports more than one thing, such as a component plus a related type.
This file-per-component pattern (sometimes grouped into per-component folders as a project grows) keeps each piece of UI small, testable, and independently readable, while the import graph — which files import which — visibly represents your component tree's structure.
Composing the tree: App, Header, Main, WelcomeCard, Footer
App is the root component of the application (rendered by main.tsx). Its job is mostly composition: it imports and arranges its children in the order they should appear, rather than containing much markup or logic of its own. Here, App renders a Header, then a Main section, then a Footer, laid out top to bottom exactly as JSX describes them — JSX siblings render in the order they're written, just like HTML.
Main in turn renders a WelcomeCard as its child, one level deeper in the tree. Nesting like this — a component rendering a component that itself renders another component — is completely ordinary and is how real applications scale to hundreds of components: each one stays small and focused, and the tree grows deeper and wider as the app grows, not any single file.
Rendering the whole tree simply means: main.tsx renders <App />; React calls App, which returns JSX referencing <Header />, <Main />, and <Footer />; React then calls each of those in turn, recursively expanding the tree down to plain HTML elements (<header>, <div>, <footer>, and so on) at the leaves, which is what ultimately becomes real DOM nodes in the page.
A minimal component definition, export, and import
The import path "./WelcomeCard" omits the file extension — Vite's TypeScript/module resolution finds WelcomeCard.tsx automatically. A component's name must start with a capital letter (WelcomeCard, not welcomeCard) — JSX uses this casing rule to tell apart your components (<WelcomeCard />) from built-in HTML tags (<div>).
// Defining and exporting a component (WelcomeCard.tsx)
function WelcomeCard() {
return (
<div className="welcome-card">
<h2>Welcome!</h2>
<p>Glad you're here.</p>
</div>
);
}
export default WelcomeCard;
// Importing and using it elsewhere (Main.tsx)
import WelcomeCard from "./WelcomeCard";
function Main() {
return (
<main>
<WelcomeCard />
</main>
);
}
export default Main;Code Examples
Five small files, each exporting one component. App imports and composes Header, Main, and Footer as siblings; Main in turn imports and renders WelcomeCard as its child, producing the required App -> Header / Main -> WelcomeCard / Footer tree. main.tsx (unchanged from the Vite template) renders <App /> into the page, which cascades through this whole tree. Expected result: The rendered page shows, top to bottom: an 'SampleApp' heading, a welcome card with 'Welcome!' and its message, and a copyright footer line.
// Header.tsx
function Header() {
return (
<header>
<h1>SampleApp</h1>
</header>
);
}
export default Header;
// WelcomeCard.tsx
function WelcomeCard() {
return (
<div className="welcome-card">
<h2>Welcome!</h2>
<p>Start learning React with TypeScript.</p>
</div>
);
}
export default WelcomeCard;
// Main.tsx
import WelcomeCard from "./WelcomeCard";
function Main() {
return (
<main>
<WelcomeCard />
</main>
);
}
export default Main;
// Footer.tsx
function Footer() {
return (
<footer>
<p>© 2026 SampleApp</p>
</footer>
);
}
export default Footer;
// App.tsx
import Header from "./Header";
import Main from "./Main";
import Footer from "./Footer";
function App() {
return (
<>
<Header />
<Main />
<Footer />
</>
);
}
export default App;The component tree for this lesson
App is the root; Header, Main, and Footer are its direct children (siblings, rendered top to bottom); WelcomeCard is nested one level deeper, as Main's own child.
App
├── Header
├── Main
│ └── WelcomeCard
└── FooterImportant Rules
Component names must be capitalized (PascalCase) so JSX can distinguish <MyComponent /> (a component) from <div> (a built-in HTML element).
Each component file typically has one export default, imported under whatever local name the importing file chooses.
JSX can only return a single root element from a component — wrap multiple sibling elements in a parent element or a Fragment (<>...</>).
Composition (components rendering other components) is the primary way React applications are structured — prefer many small components over one large one.
Where this shows up
Every production React application — no matter how large — is this same pattern repeated hundreds or thousands of times: small, single-purpose components in their own files, composed together through imports into ever-larger trees, starting from one root App component. The App/Header/Main/Footer shape here is essentially the skeleton of nearly every real website layout you'll build in React.
function App() { return ( <Header /> <Main /> <Footer />); } // Error: JSX expressions must have one parent element function App() { return ( <> <Header /> <Main /> <Footer /> </>); }
A component's returned JSX must have exactly one root/top-level element. Listing multiple siblings directly is a syntax error; wrapping them in a Fragment (<>...</>) or a real element (like a <div>) fixes it without adding an unwanted wrapper node to the DOM.
[multiple-choice] In the App/Header/Main/WelcomeCard/Footer tree, what is WelcomeCard's relationship to App? A. WelcomeCard is App's direct child, same level as Header and Footer B. WelcomeCard is a grandchild of App, nested inside Main C. WelcomeCard and App are unrelated, siblings at the top level D. WelcomeCard renders App inside it
B. WelcomeCard is a grandchild of App, nested inside Main — WelcomeCard is rendered inside Main, and Main is rendered inside App — so WelcomeCard is nested two levels down from App, making it a grandchild, not a direct child.
[multiple-choice] Why must component function names start with a capital letter? A. It's just a style preference with no functional effect B. JSX uses the capitalization to distinguish a custom component (<MyComp />) from a built-in HTML tag (<div>) C. TypeScript requires all functions to be capitalized D. Lowercase component names cause a runtime crash on import
B. JSX uses the capitalization to distinguish a custom component (<MyComp />) from a built-in HTML tag (<div>) — JSX's compiler treats a lowercase tag name as a built-in HTML element string (like "div") and a capitalized tag name as a reference to a component variable — so an uncapitalized component wouldn't be recognized as a component at all.
[predict-output] If Main.tsx forgets to import WelcomeCard but still writes <WelcomeCard /> in its JSX, what happens? A. It silently renders nothing extra B. TypeScript/the bundler reports that WelcomeCard is not defined (an unresolved reference) C. React automatically finds and imports it D. It renders the text 'WelcomeCard' literally on the page
B. TypeScript/the bundler reports that WelcomeCard is not defined (an unresolved reference) — JSX component references are ordinary variable references under the hood — using <WelcomeCard /> without importing (or otherwise defining) WelcomeCard in that file is a 'cannot find name' error, caught by TypeScript/the build tool before the app even runs.
Add a Sidebar component (its own file, exporting a <aside> with some placeholder text) and render it inside Main, alongside WelcomeCard, so Main's JSX returns both as siblings.
// Sidebar.tsx function Sidebar() { return <aside>Related links</aside>; } export default Sidebar; // Main.tsx import WelcomeCard from "./WelcomeCard"; import Sidebar from "./Sidebar"; function Main() { return ( <main> <WelcomeCard /> <Sidebar /> </main> ); } export default Main;
Main's returned JSX needs a single root element wrapping both children — use a Fragment or a wrapping <div>.