I Built a Profile Card With Zero Divs — Here's the Challenge
Prerequisites
- A code editor (VS Code recommended)
- A modern browser (Chrome, Firefox, or Edge)
- Basic understanding of what HTML tags and CSS properties are
The Constraint
<div> tags allowed. Every piece of content must use a semantic HTML5 element. If you reach for a <div>, stop and find the right element.I used to wrap everything in <div> tags. It works, but it tells the browser — and screen readers — nothing about the content. So I gave myself this constraint to learn the right element for each role. By the end, I used 7 semantic elements, 5 CSS selector types, and finally understood when to use rem vs em vs px. Here's the full walkthrough — and the challenge for you to try it yourself.
What I Built
A centered profile card on a dark page — white rectangle, max-width 480px, rounded corners, shadow. It displays a developer's photo, bio, skills, and contact links. Here's the final result:
I broke the card into these sections from top to bottom: a header with the photo, name, and title. An About section with a short bio. A Skills section with wrapped pill badges. A Fun Fact callout box. A Contact section with email and social links. And a footer with copyright text. Each separated by a thin border divider.
I used these 8 semantic elements — no <div> anywhere:
| Element | Meaning | Where |
|---|---|---|
| <main> | Primary page content (one per page) | Wraps the entire card |
| <article> | Self-contained content | The profile card itself |
| <header> | Introductory content for its parent | Photo + name + title |
| <section> | Thematic grouping with a heading | About, Skills sections |
| <aside> | Tangentially related content | Fun fact callout |
| <nav> | Navigation links | Contact links |
| <footer> | Footer for its parent | Copyright notice |
| <figure> | Self-contained media | Profile photo |
Step 1 — Project Setup + CSS Reset
I started with two files: index.html and styles.css. The foundation — boilerplate every project starts with.
Challenge: Try writing the HTML boilerplate yourself — with lang="en", the viewport meta tag, and a linked stylesheet. Then check my version below.
Design Tokens
I defined every color, size, and spacing value as CSS custom properties upfront. This is the full design token block I used:
Step 2 — Page Background + Card Centering
Next I needed the dark navy page with a white card centered in the middle. Flexbox does the heavy lifting here. I wrapped the content in <main> and <article> — no <div> needed.
Step 3 — Card Header (Photo, Name, Title)
The introductory part of the card: a circular photo, bold name, and muted job title. I used <header> for the intro and <figure> for the photo, with border-radius: 50% to make it circular.
Step 4 — About Section
A thematic grouping with a heading — exactly what <section> is for. I styled the heading as a small uppercase label to act as a section marker.
<section> is a thematic grouping that needs its parent for context. The "About" section only makes sense as part of this card. An <article> stands alone — you could paste it on any other page and it still makes sense.em is relative to the element's own font size. If you change the h2 font-size, letter-spacing scales proportionally. rem would stay fixed relative to the root, potentially looking too tight or too loose.Step 5 — Section Dividers (The + Combinator)
No extra HTML needed here. I used the adjacent sibling combinator in CSS to add borders between sections without giving the first section an unwanted top border.
section + section means "a section that directly follows another section." It skips the first one — no unwanted top border on About. Borders in px: Borders should be exactly 1px. Using rem could round to 0px or 2px at different font sizes.Step 6 — Skill Pills (Flexbox + nth-child)
This was a fun one — pill badges that wrap to new lines, with alternating styles using :nth-child. No extra classes needed in the HTML.
Step 7 — Fun Fact Aside
I wanted a fun fact callout — content tangentially related to the main profile. <aside> is the semantic element for exactly this. Screen readers identify it as supplementary content that users can skip.
Step 8 — Contact Links (Attribute Selectors)
I styled links differently based on their href attribute — no extra classes needed. The email link turns red automatically using [href^="mailto:"]. This was one of the more satisfying CSS tricks in the build.
Step 9 — Footer
<footer> isn't limited to the page bottom. It represents footer content for its nearest sectioning ancestor — here, the <article>. The card's copyright belongs inside the card's footer.
Step 10 — Card Hover + Mobile Responsiveness
The finishing touches: I added a deeper shadow on hover (the transition was already set in Step 2), and responsive adjustments for mobile.
Acceptance Criteria
Here's the checklist I used to verify the build:
- Zero <div> tags in the HTML
- Uses all 7 semantic elements: <header>, <main>, <article>, <section> (×2), <aside>, <nav>, <footer>
- Uses <figure> with <img> for the profile photo with meaningful alt text
- 5 CSS selector types: class, descendant, pseudo-class, attribute, combinator
- Hover effects on card, links, and skill pills
- :focus styles on all links (keyboard accessible)
- Uses rem for fonts, em for pill padding, px for borders/shadows, % for widths, vh for body height
- All values use CSS custom properties from :root
- Responsive: fills width on mobile (< 768px), centered with max-width on desktop
- Valid HTML — passes W3C Validator
Quick Reference
CSS Units — When to Use What
| Unit | Relative To | Use For | Example |
|---|---|---|---|
| rem | Root font size (16px) | Font sizes, spacing | font-size: 1.75rem |
| em | Element's own font size | Component-scoped padding | padding: 0.375em |
| px | Absolute | Borders, shadows, decorative | border: 1px solid |
| % | Parent dimension | Widths, radius | width: 100% |
| vh | Viewport height | Full-page layouts | min-height: 100vh |
CSS Selector Types Used
| Type | Syntax | Example |
|---|---|---|
| Class | .name | .profile-card, .skills-list |
| Descendant | A B | .profile-card header h1 |
| Pseudo-class | :state | :hover, :focus, :nth-child(even) |
| Attribute | [attr] | a[href^="mailto:"] |
| Adjacent sibling | A + B | section + section |
Bonus Challenges
Finished the main build? Here are some extra challenges I've been experimenting with — no JavaScript allowed for any of them.
1. Dark Mode with :has()
Add a <details><summary>Toggle Dark Mode</summary></details> at the top. Use body:has(details[open]) to swap CSS variables to dark colors. Zero JavaScript.
2. Print Stylesheet
Add @media print { } that hides the photo, removes backgrounds, converts shadows to borders, and makes all text black.
3. Animated Entrance
Use @keyframes fadeSlideUp with opacity: 0 → 1 and translateY(20px) → 0. Apply to the card on page load.
4. Link Icons via ::before
Add pseudo-elements with icons (✉, 💻, 🔗, 🌐) before each contact link. Give them a fixed width so icons align vertically.
5. Skill Proficiency Bars
Add a <meter> element after each skill. Style it to show a colored progress bar (React: 90%, Python: 60%, etc).