Build a static Tweet card — the layout of a single post as it appears in a timeline. There's no interactivity to wire up: the whole exercise is structure and layout. You'll lay out the classic two-column shape — a round avatar on the left, and a content column on the right holding the author line, the post text, and a row of action buttons (reply, retweet, like, views). This is the bread-and-butter of UI work: turning a design into clean, semantic markup that lines up correctly.
// A self-contained, presentational component. No props, no state.
function App(): JSX.Element;
Render one tweet with an avatar, an author line, body text, and an action row.
┌────────────────────────────────────────────┐
│ (AL) Ada Lovelace @ada · 2h │
│ Just shipped my first component in │
│ four frameworks… │
│ reply 24 retweet 18 like 312 │
└────────────────────────────────────────────┘
Layout shape:
[ avatar ][ body: header line / text / actions ]
fixed width flexes to fill the rest
display: flex) with the avatar non-shrinking and the body flexing is the natural fit.<article>; the author line and actions are just rows inside it. Don't reach for a <table> or absolute positioning.You'll turn a familiar design — a single tweet — into clean, semantic markup whose two columns line up using flexbox rather than floats or guesswork.
A tweet is a card with two columns: a round avatar on the left, and everything else on the right. The right column stacks three rows — the author line (name, handle, time), the post text, and a row of actions. There's no behaviour to build; the skill on show is decomposing a design into nested boxes and picking the layout primitive that makes them line up. The right primitive here is flexbox.
Think of the card as an outer flex row holding two children: a fixed-size avatar and a flexible body. The avatar must not shrink; the body should take all the remaining width. Inside the body, the three rows simply stack in normal document flow (block elements, top to bottom). The action row is itself a small flex row that spreads its items out. So it's flex on the outside (two columns), block in the middle (three stacked rows), flex again at the bottom (spaced actions).
A common first instinct is to float the avatar and let the text wrap around it:
<article className="tweet">
<div className="avatar" style={{ float: 'left' }}>AL</div>
<div className="header">Ada Lovelace @ada · 2h</div>
<p className="text">Just shipped my first component…</p>
<div className="actions">…</div>
</article>
Floats look right until the content is short or long: the text doesn't form a true column, so a short tweet leaves the body wrapping awkwardly under the avatar, and the action row can slide beneath it. Floats were a workaround from before we had real layout tools — there's no explicit "two columns" relationship, so alignment is accidental rather than guaranteed.
import './styles.css';
export default function App() {
return (
<main className="container">
<h1>Tweet</h1>
{/* Outer flex row: avatar | body */}
<article className="tweet">
<div className="avatar">AL</div>
{/* Body flexes to fill the rest; rows stack inside it */}
<div className="body">
<div className="header">
<span className="name">Ada Lovelace</span>
<span className="handle">@ada</span>
<span className="dot">·</span>
<span className="time">2h</span>
</div>
<p className="text">
Just shipped my first component in four frameworks. Same question,
four ways — React, Vue, Angular, and Vanilla. Wild how the ideas
carry over.
</p>
<div className="actions">
<span className="action"><span className="icon">💬</span>24</span>
<span className="action"><span className="icon">🔁</span>18</span>
<span className="action"><span className="icon">♥</span>312</span>
<span className="action"><span className="icon">📊</span>12K</span>
</div>
</div>
</article>
</main>
);
}
The key shifts from the float version: the card is display: flex, so the avatar and body are declared columns, not visually-coincidental ones. The avatar gets flex-shrink: 0 (keep its size), and the body gets flex: 1 with min-width: 0 (take the rest, and allow long words to wrap instead of overflowing). The actions are their own display: flex row with justify-content: space-between.
There's no runtime behaviour to trace, so let's trace the layout the browser computes for the markup above:
.avatar and .body — are placed side by side on the main (horizontal) axis.flex-shrink: 0 and a fixed 48px width, it never gives ground, even if the body's text is long.flex: 1 expands it to fill the remaining width; min-width: 0 lets a long unbroken word wrap rather than push the column wider than its share..header, .text, and .actions are block-level, so they sit top to bottom. .actions then runs its own flex layout to space the four items out.Change the tweet text to one word or three paragraphs and the columns stay aligned — that's the difference between a declared layout and an accidental one.
float: left only looks like two columns; short or long content breaks the alignment. Fix: make the card display: flex so the columns are explicit.min-width: 0 on the flex body. A long unbroken string (a URL, say) can force the body wider than its share and overflow the card. Fix: min-width: 0 lets the content wrap.flex-shrink: 0, a long tweet squeezes the avatar into an oval. Fix: pin it with flex-shrink: 0 and a fixed width/height.name, handle, text, and counts in so the same card renders any tweet — the first step toward a real timeline.import './styles.css';
const ACTIONS = [
['💬', '24'],
['🔁', '18'],
['♥', '312'],
['📊', '12K'],
] as const;
export default function App() {
return (
<main className="container">
<h1>Tweet</h1>
<article className="tweet">
<div className="avatar">AL</div>
<div className="body">
<div className="header">
<span className="name">Ada Lovelace</span>
<span className="handle">@ada</span>
<span className="dot">·</span>
<span className="time">2h</span>
</div>
<p className="text">
Just shipped my first component in four frameworks. Same question,
four ways — React, Vue, Angular, and Vanilla. Wild how the ideas
carry over.
</p>
<div className="actions">
{ACTIONS.map(([icon, count]) => (
<span className="action" key={count}><span className="icon">{icon}</span>{count}</span>
))}
</div>
</div>
</article>
</main>
);
}Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
Build a static Tweet card — the layout of a single post as it appears in a timeline. There's no interactivity to wire up: the whole exercise is structure and layout. You'll lay out the classic two-column shape — a round avatar on the left, and a content column on the right holding the author line, the post text, and a row of action buttons (reply, retweet, like, views). This is the bread-and-butter of UI work: turning a design into clean, semantic markup that lines up correctly.
// A self-contained, presentational component. No props, no state.
function App(): JSX.Element;
Render one tweet with an avatar, an author line, body text, and an action row.
┌────────────────────────────────────────────┐
│ (AL) Ada Lovelace @ada · 2h │
│ Just shipped my first component in │
│ four frameworks… │
│ reply 24 retweet 18 like 312 │
└────────────────────────────────────────────┘
Layout shape:
[ avatar ][ body: header line / text / actions ]
fixed width flexes to fill the rest
display: flex) with the avatar non-shrinking and the body flexing is the natural fit.<article>; the author line and actions are just rows inside it. Don't reach for a <table> or absolute positioning.You'll turn a familiar design — a single tweet — into clean, semantic markup whose two columns line up using flexbox rather than floats or guesswork.
A tweet is a card with two columns: a round avatar on the left, and everything else on the right. The right column stacks three rows — the author line (name, handle, time), the post text, and a row of actions. There's no behaviour to build; the skill on show is decomposing a design into nested boxes and picking the layout primitive that makes them line up. The right primitive here is flexbox.
Think of the card as an outer flex row holding two children: a fixed-size avatar and a flexible body. The avatar must not shrink; the body should take all the remaining width. Inside the body, the three rows simply stack in normal document flow (block elements, top to bottom). The action row is itself a small flex row that spreads its items out. So it's flex on the outside (two columns), block in the middle (three stacked rows), flex again at the bottom (spaced actions).
A common first instinct is to float the avatar and let the text wrap around it:
<article className="tweet">
<div className="avatar" style={{ float: 'left' }}>AL</div>
<div className="header">Ada Lovelace @ada · 2h</div>
<p className="text">Just shipped my first component…</p>
<div className="actions">…</div>
</article>
Floats look right until the content is short or long: the text doesn't form a true column, so a short tweet leaves the body wrapping awkwardly under the avatar, and the action row can slide beneath it. Floats were a workaround from before we had real layout tools — there's no explicit "two columns" relationship, so alignment is accidental rather than guaranteed.
import './styles.css';
export default function App() {
return (
<main className="container">
<h1>Tweet</h1>
{/* Outer flex row: avatar | body */}
<article className="tweet">
<div className="avatar">AL</div>
{/* Body flexes to fill the rest; rows stack inside it */}
<div className="body">
<div className="header">
<span className="name">Ada Lovelace</span>
<span className="handle">@ada</span>
<span className="dot">·</span>
<span className="time">2h</span>
</div>
<p className="text">
Just shipped my first component in four frameworks. Same question,
four ways — React, Vue, Angular, and Vanilla. Wild how the ideas
carry over.
</p>
<div className="actions">
<span className="action"><span className="icon">💬</span>24</span>
<span className="action"><span className="icon">🔁</span>18</span>
<span className="action"><span className="icon">♥</span>312</span>
<span className="action"><span className="icon">📊</span>12K</span>
</div>
</div>
</article>
</main>
);
}
The key shifts from the float version: the card is display: flex, so the avatar and body are declared columns, not visually-coincidental ones. The avatar gets flex-shrink: 0 (keep its size), and the body gets flex: 1 with min-width: 0 (take the rest, and allow long words to wrap instead of overflowing). The actions are their own display: flex row with justify-content: space-between.
There's no runtime behaviour to trace, so let's trace the layout the browser computes for the markup above:
.avatar and .body — are placed side by side on the main (horizontal) axis.flex-shrink: 0 and a fixed 48px width, it never gives ground, even if the body's text is long.flex: 1 expands it to fill the remaining width; min-width: 0 lets a long unbroken word wrap rather than push the column wider than its share..header, .text, and .actions are block-level, so they sit top to bottom. .actions then runs its own flex layout to space the four items out.Change the tweet text to one word or three paragraphs and the columns stay aligned — that's the difference between a declared layout and an accidental one.
float: left only looks like two columns; short or long content breaks the alignment. Fix: make the card display: flex so the columns are explicit.min-width: 0 on the flex body. A long unbroken string (a URL, say) can force the body wider than its share and overflow the card. Fix: min-width: 0 lets the content wrap.flex-shrink: 0, a long tweet squeezes the avatar into an oval. Fix: pin it with flex-shrink: 0 and a fixed width/height.name, handle, text, and counts in so the same card renders any tweet — the first step toward a real timeline.import './styles.css';
const ACTIONS = [
['💬', '24'],
['🔁', '18'],
['♥', '312'],
['📊', '12K'],
] as const;
export default function App() {
return (
<main className="container">
<h1>Tweet</h1>
<article className="tweet">
<div className="avatar">AL</div>
<div className="body">
<div className="header">
<span className="name">Ada Lovelace</span>
<span className="handle">@ada</span>
<span className="dot">·</span>
<span className="time">2h</span>
</div>
<p className="text">
Just shipped my first component in four frameworks. Same question,
four ways — React, Vue, Angular, and Vanilla. Wild how the ideas
carry over.
</p>
<div className="actions">
{ACTIONS.map(([icon, count]) => (
<span className="action" key={count}><span className="icon">{icon}</span>{count}</span>
))}
</div>
</div>
</article>
</main>
);
}Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.