On this page
This project turns a small amount of HTML and CSS into a complete, testable interface. You will build a profile card with a name, role, short introduction, skills, and two useful links.
The card will use normal document flow on narrow screens and switch to a two-part header when there is enough room. No JavaScript is required.
Final goal
The finished page should:
- remain readable without horizontal scrolling;
- use semantic headings, paragraphs, lists, and links;
- keep line lengths comfortable;
- let buttons wrap on small screens;
- show a strong keyboard focus indicator;
- respond to available space instead of targeting a particular phone model.
Create the project files
Make a folder called profile-card with two files:
profile-card/
├── index.html
└── styles.css
- Create the semantic card structure in
index.html. - Connect
styles.cssin the document head. - Add mobile-first base styles.
- Enhance the card header when more space is available.
- Test content, focus, zoom, and several viewport widths.
Write the semantic HTML
Use an <article> because this profile can stand on its own. A real heading names it. The skill names form a list, and the actions are links because they navigate to another location.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
name="description"
content="A responsive profile card built with semantic HTML and CSS."
/>
<title>Responsive profile card</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<main class="page-shell">
<article class="profile-card">
<header class="profile-header">
<div class="avatar" aria-hidden="true">RC</div>
<div>
<p class="eyebrow">Frontend learner</p>
<h1>Riley Chen</h1>
<p class="location">Colombo, Sri Lanka · Open to collaboration</p>
</div>
</header>
<div class="profile-body">
<p class="introduction">
I build clear, accessible web pages and document what I learn along the way.
</p>
<section aria-labelledby="skills-title">
<h2 id="skills-title">Current skills</h2>
<ul class="skills-list">
<li>Semantic HTML</li>
<li>Modern CSS</li>
<li>Responsive design</li>
</ul>
</section>
<nav class="profile-actions" aria-label="Profile links">
<a class="primary-link" href="https://github.com/your-name"> View GitHub profile </a>
<a class="secondary-link" href="https://example.com"> Visit personal website </a>
</nav>
</div>
</article>
</main>
</body>
</html>
The initials are decorative because the nearby <h1> already provides the person’s full name. aria-hidden="true" prevents a screen reader from announcing redundant letters. The navigation has an accessible label that explains what its two links are.
Replace the example destinations before publishing your own card. A visible link should never pretend to lead somewhere it does not.
Add the mobile-first CSS
The base rules work at narrow widths. The card’s width uses min() so it can grow up to a readable maximum but always leaves a margin on a small screen.
:root {
color-scheme: light;
font-family:
Inter,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
background: #f2f1ec;
color: #17203a;
}
* {
box-sizing: border-box;
}
body {
min-width: 20rem;
margin: 0;
}
a {
color: inherit;
}
.page-shell {
min-height: 100vh;
display: grid;
place-items: center;
padding: clamp(1rem, 5vw, 4rem);
}
.profile-card {
width: min(100%, 43rem);
overflow: hidden;
border: 1px solid #cdd2df;
border-radius: 1.25rem;
background: #ffffff;
box-shadow: 0 1.25rem 3.5rem rgb(23 32 58 / 12%);
}
.profile-header {
display: flex;
flex-direction: column;
gap: 1rem;
padding: clamp(1.5rem, 6vw, 2.5rem);
background: #121a31;
color: #f8f9ff;
}
.avatar {
width: 4rem;
aspect-ratio: 1;
display: grid;
place-items: center;
flex: 0 0 auto;
border: 1px solid rgb(255 255 255 / 28%);
border-radius: 50%;
background: #3946d6;
font-weight: 800;
letter-spacing: 0.04em;
}
.eyebrow,
.location {
margin: 0;
}
.eyebrow {
color: #85e9f3;
font-size: 0.78rem;
font-weight: 800;
letter-spacing: 0.1em;
text-transform: uppercase;
}
h1 {
margin: 0.35rem 0 0.55rem;
font-size: clamp(2rem, 8vw, 3.25rem);
line-height: 1;
}
.location {
color: #cbd2e6;
line-height: 1.5;
}
.profile-body {
padding: clamp(1.5rem, 6vw, 2.5rem);
}
.introduction {
max-width: 55ch;
margin: 0;
font-size: 1.1rem;
line-height: 1.7;
}
h2 {
margin: 2rem 0 0.8rem;
font-size: 1rem;
}
.skills-list {
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
margin: 0;
padding: 0;
list-style: none;
}
.skills-list li {
padding: 0.45rem 0.7rem;
border: 1px solid #d9ddec;
border-radius: 999px;
background: #f5f6fc;
font-size: 0.9rem;
}
.profile-actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 2rem;
}
.profile-actions a {
min-height: 2.75rem;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.7rem 1rem;
border: 2px solid transparent;
border-radius: 0.7rem;
font-weight: 750;
text-decoration: none;
}
.primary-link {
background: #3946d6;
color: #ffffff;
}
.profile-actions .secondary-link {
border-color: #c6ccdf;
background: #ffffff;
}
.profile-actions a:hover {
text-decoration: underline;
text-underline-offset: 0.2em;
}
.profile-actions a:focus-visible {
outline: 3px solid #00a7bd;
outline-offset: 3px;
}
@media (width >= 36rem) {
.profile-header {
flex-direction: row;
align-items: center;
}
.avatar {
width: 5rem;
}
}
The only breakpoint appears when the content has enough space for a row. The design does not assume a named device. Flexible padding, a maximum card width, wrapped skill labels, and wrapped actions do most of the responsive work.
Test at multiple widths
Open index.html, then resize the browser or use its responsive design tools. Check 390px, 430px, 768px, and a wide desktop view. The exact breakpoint matters less than observing where the content becomes uncomfortable.
Testing checklist
- No page-level horizontal scrollbar appears.
- The name and introduction remain readable at 200% zoom.
- Each link has a visible focus ring when reached with the Tab key.
- The links wrap instead of leaving the card.
- The skill list can grow to a second line.
- The page still makes sense if the stylesheet does not load.
- Example profile URLs have been replaced before publishing.
Common mistakes
Giving the card a fixed pixel width. width: 700px will overflow many phone screens. Combine percentages with a maximum instead.
Using a <div> for every piece of text. Headings, paragraphs, lists, and links carry meaning and useful default behavior.
Removing focus outlines without a replacement. Keyboard users need to see which link is active.
Choosing a breakpoint from a device list. Resize the content and add a breakpoint where the design needs one.
Making a navigation control a button. These actions go to URLs, so links are the correct element. A button is for an action on the current page.
Practice
Optional improvements
Add a short “Currently learning” section. Try a four-item skill list with longer text. Then create a dark-theme version by changing colors through custom properties rather than duplicating every rule.
What to learn next
If any spacing or wrapping decision felt uncertain, review the CSS box model and the responsive Flexbox layout lesson. Then turn the card into a reusable Astro component with typed props for the name, role, skills, and links. That next step will show why reusable components are valuable while the semantic HTML and responsive CSS stay almost unchanged.