Search the library

What would you like to learn?

Type two or more characters to search.

You can also browse all tutorials or topics.

Practical project

Build a Responsive Profile Card with HTML and CSS

Build a polished profile card with semantic HTML, flexible CSS, accessible links, and a practical responsive testing checklist.

HTMLCSSResponsive designAccessibility

You will learn

  • Structure a standalone card with semantic HTML
  • Build a flexible layout without fixed device widths
  • Style links with clear hover and keyboard-focus states
  • Test the result at narrow and wide viewport sizes

Before you start

  • Basic HTML elements and attributes
  • Basic CSS selectors and properties
  • A text editor and modern browser

Project outcome

What you will finish with

A complete profile card that adapts from a compact phone layout to a balanced wider layout without page-level horizontal scrolling.

Skills practiced

HTMLCSSResponsive designAccessibility
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
  1. Create the semantic card structure in index.html.
  2. Connect styles.css in the document head.
  3. Add mobile-first base styles.
  4. Enhance the card header when more space is available.
  5. 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.

index.htmlhtml
<!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.

styles.csscss
: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.

Keep exploring

Reference material

Official documentation