A step-by-step guide to building a responsive, animated profile card using only HTML and CSS — no frameworks required.
If you're searching for a responsive profile card built with HTML and CSS, you're in exactly the right place. This guide gives you the complete, working source code along with a thorough explanation of every decision — from the HTML skeleton to the SCSS architecture — so you don't just copy and paste, you actually understand what's happening.
In my experience teaching front-end fundamentals, the profile card is one of the most requested beginner projects for a good reason: it forces you to deal with circular images, CSS gradients, hover animations, and responsive breakpoints all at once. Master this component and you'll have the intuition to build virtually any UI card going forward.
I'm Mostafa Amaan, and throughout this guide I'll walk you through the full project from an empty folder to a polished, animated card — explaining not just the how but the why behind each line of code.
What You'll Build in This Guide
By the end of this tutorial you'll have a fully functional profile card that includes:
- A circular profile photo with a decorative colored border
- User name and job title
- Clickable social media icon links
- An info button that reveals extra details on hover
- A layout that adapts cleanly to mobile, tablet, and desktop screens
The finished product: an animated, responsive CSS profile card.
What You Need Before You Start
This project is beginner-friendly, but you'll get the most out of it if you already have a basic grasp of:
| Skill | Level Required | Where to Learn |
|---|---|---|
| HTML | Basic (tags & structure) | Programming Languages Guide |
| CSS | Basic (Box Model, Flexbox) | — |
| SCSS | Optional — explained in this article | Covered below |
Setting Up the Project Structure
The project is made up of seven files organized in a clean, scalable folder structure:
-
index.html— Main HTML file -
assets/css/styles.css— Compiled CSS output -
assets/img/perfil.png— Profile photo -
assets/scss/styles.scss— SCSS entry point -
assets/scss/base/_base.scss— Global resets and base styles -
assets/scss/components/_card.scss— Card component styles -
assets/scss/config/_variables.scss— Design tokens (colors, fonts, sizes)
This structure follows the SCSS Partials pattern — a
widely-used approach in professional front-end projects where styles are
split into small, focused files (called partials, prefixed with
_)
and then imported into a single entry point. It might feel like extra
overhead for a small project, but learning this pattern now will save you
enormous time when your projects grow.
├── index.html
└── assets/
├── css/
│ └── styles.css
├── img/
│ └── perfil.png
└── scss/
├── styles.scss
├── base/
│ └── _base.scss
├── components/
│ └── _card.scss
└── config/
└── _variables.scss
Step 1 — Building the HTML Structure (index.html)
Start by creating the project's root folder — I named mine
card-info, but you can call it anything. Inside, create
index.html.
This is the skeleton that holds every visual element together.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!--=============== Icons ===============-->
<link href="https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css"
rel="stylesheet">
<!--=============== CSS ===============-->
<link rel="stylesheet" href="assets/css/styles.css">
<title>Profile Card — Valley4Techs</title>
</head>
<body>
<div class="container">
<!-- Card markup goes here —
full code available in the GitHub repository -->
</div>
</body>
</html>
Line-by-line breakdown:
-
<!DOCTYPE html>— Tells the browser this is an HTML5 document. Always include it; without it, older browsers enter quirks mode and your layout breaks unpredictably. -
lang="en"— Sets the document language. Screen readers and search engines both use this attribute. -
charset="UTF-8"— Ensures special characters, em dashes, and emoji render correctly in every browser. -
name="viewport"— The single most important meta tag for responsive design. Without it, mobile browsers render the page at desktop width and then scale it down, making everything tiny. - Remixicon CDN link — Loads the icon library we use for social media buttons. More on this library below.
Step 2 — The Main CSS File (styles.css)
Create the folder path assets/css/
and inside it a file named styles.css.
If you're working with plain CSS (no SCSS compiler), this is where all your
styles will live. If you're using SCSS, this file is the
compiled output — you won't edit it directly.
Here's the core of the file — the CSS custom properties (design tokens) and Google Fonts import:
/*=============== Google Fonts ===============*/
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&display=swap");
/*=============== CSS Variables ===============*/
:root {
/* Colors — HSL format (hue, saturation, lightness) */
--first-color: hsl(175, 55%, 40%);
--first-color-light: hsl(175, 30%, 48%);
--first-color-lighten: hsl(175, 40%, 62%);
--gradient-color: linear-gradient(
180deg,
hsl(178, 35%, 45%),
hsl(178, 55%, 28%)
);
--title-color: hsl(175, 40%, 16%);
--text-color: hsl(175, 8%, 50%);
--white-color: #fff;
--body-color: hsl(175, 100%, 99%);
/* Typography — .5rem = 8px | 1rem = 16px */
--body-font: "Poppins", sans-serif;
--h2-font-size: 1.25rem;
--normal-font-size: .938rem;
--smaller-font-size: .75rem;
}
/*=============== Base Styles ===============*/
/* Full code available in the GitHub repository */
Why CSS variables? A common mistake I see in beginner
projects is hard-coding color values in dozens of places. When the client
asks you to change the brand green to a brand blue, you end up doing a
find-and-replace that inevitably misses something. By defining all colors
in :root,
you change one line and the entire card updates. It's the same principle
behind design tokens in frameworks like Tailwind or Material UI.
Why HSL colors? HSL (Hue, Saturation, Lightness) is far more intuitive for hand-writing color variations than hex or RGB. To make a color darker, just decrease the lightness value. To make it more muted, decrease saturation. You'll notice the entire palette in this project is built on a single hue (175–178°, a teal-green), with only saturation and lightness varying — that's why everything looks cohesive.
Step 3 — Adding the Profile Image
Create assets/img/
and drop in your profile photo. The filename in the project is
perfil.png,
but you can name it anything — just update the
src
attribute in your HTML accordingly.
Step 4 — The SCSS Entry Point (styles.scss)
Create assets/scss/styles.scss.
This file's only job is to pull together the three partials:
@import 'config/variables';
@import 'base/base';
@import 'components/card';
Order matters here. Variables must be imported first
because both _base.scss and _card.scss reference
the custom properties defined in _variables.scss. If you flip
the order, the SCSS compiler will throw "undefined variable" errors.
If you've never used SCSS before and the compiler step sounds intimidating,
install the Live Sass Compiler extension in VS Code. It
watches your .scss files and automatically outputs
styles.css every time you save — zero command-line required.
Step 5 — Global Resets (_base.scss)
Create assets/scss/base/_base.scss.
This partial handles the universal reset styles that every project needs:
/*=============== Base Resets ===============*/
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
body {
font-family: var(--body-font);
font-size: var(--normal-font-size);
background-color: var(--body-color);
color: var(--text-color);
}
ul { list-style: none; }
a { text-decoration: none; }
img { max-width: 100%; height: auto; }
What each block does:
-
* { box-sizing: border-box }— This is the single most impactful CSS reset you can write. By default, browsers add padding outside an element's declared width. Withborder-box, padding is included inside the width, so awidth: 290pxelement is always exactly 290px wide — no surprises. -
padding: 0; margin: 0;— Browsers apply different default margins to headings, paragraphs, and lists. Zeroing them out means your layout behaves identically across Chrome, Firefox, and Safari. -
ul { list-style: none }— Removes the default bullet points from the social icons list. Without this, you'd see bullets next to your Twitter and GitHub icons. -
img { max-width: 100%; height: auto }— Makes every image responsive by default. The image scales down on small screens but never overflows its container.
Step 6 — Styling the Card Component (_card.scss)
Create assets/scss/components/_card.scss.
This is the heart of the project — it defines the card's shape, gradient
background, and the hover animation.
/*=============== Card ===============*/
.container {
height: 100vh;
margin-inline: 1.5rem;
display: grid;
place-items: center;
}
.card {
position: relative;
width: 290px;
background: var(--gradient-color);
border-radius: 1rem 1rem 11rem 11rem;
padding: 2.5rem 1.5rem 3.5rem;
text-align: center;
box-shadow: 0 8px 32px hsla(178, 55%, 20%, .15);
&__img {
width: 90px;
}
&__border {
/* Full code in GitHub repository */
}
&__info {
position: absolute;
bottom: -4rem; /* Hidden by default */
transition: bottom .3s ease;
}
}
/* Reveal info panel on hover and focus */
.card:hover .card__info,
.card:focus .card__info {
bottom: 1.5rem;
}
Design decisions worth noting:
-
border-radius: 1rem 1rem 11rem 11rem— The four values map to top-left, top-right, bottom-right, bottom-left. A subtle rounded top with dramatically rounded bottom corners creates that distinctive "pill bottom" shape you see in the preview GIF. -
display: grid; place-items: centeron.container— The cleanest one-liner for centering anything both horizontally and vertically. No more flexbox justify/align juggling for a centered single element. -
The hover reveal mechanism — The info panel starts at
bottom: -4rem(off-screen below the card) and transitions tobottom: 1.5remon hover. This is pure CSS — no JavaScript needed. -
:focusalongside:hover— I've noticed a lot of tutorials forget this. Hover effects don't trigger on touchscreens. Addingtabindex="0"to the card in HTML and pairing:focuswith:hoverin CSS makes the animation work when a mobile user taps the card.
Step 7 — Design Tokens (_variables.scss)
Create assets/scss/config/_variables.scss.
While styles.css already includes CSS custom properties for
runtime theming, this SCSS partial is where you define
SCSS variables — values used during compilation, before
the browser ever sees the file.
/*=============== Google Fonts ===============*/
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500');
/*=============== CSS Variables ===============*/
:root {
/* Colors */
--first-color: hsl(175, 55%, 40%);
--first-color-light: hsl(175, 30%, 48%);
--first-color-lighten: hsl(175, 40%, 62%);
--gradient-color: linear-gradient(
180deg,
hsl(178, 35%, 45%),
hsl(178, 55%, 28%)
);
--title-color: hsl(175, 40%, 16%);
--text-color: hsl(175, 8%, 50%);
--white-color: #fff;
--body-color: hsl(175, 100%, 99%);
/* Typography */
--body-font: 'Poppins', sans-serif;
--h2-font-size: 1.25rem;
--normal-font-size: .938rem;
--smaller-font-size: .75rem;
}
Making the Profile Card Truly Responsive
The number-one question I get from beginners is: "Why does my card look great on desktop but break on my phone?" Usually the culprit is one of two things.
1. Use relative units instead of fixed pixels
A common mistake I see in portfolio projects is writing
font-size: 15px
for everything. This project uses rem for spacing and
typography. Since 1rem = 16px by default, the math is
straightforward — but more importantly, if a user has bumped up their
browser's default font size for accessibility, your layout scales with
them instead of overriding their preference.
2. Add media queries for edge cases
Append these breakpoints at the bottom of
_card.scss:
/*=============== Breakpoints ===============*/
/* Small devices — max 320px */
@media screen and (max-width: 320px) {
.card {
width: 250px;
padding: 2rem 1rem 3rem;
}
}
/* Medium devices — 576px and up */
@media screen and (min-width: 576px) {
.card {
width: 320px;
}
}
Common CSS Profile Card Mistakes (and How to Avoid Them)
After reviewing dozens of beginner CSS projects, these are the mistakes that come up most often:
| Mistake | Root Cause | Fix |
|---|---|---|
| Image doesn't clip to a circle | Missing overflow: hidden on the wrapper |
Add overflow: hidden to the image container alongside border-radius: 50% |
| SCSS won't compile | No SCSS compiler installed | Install the Live Sass Compiler extension in VS Code |
| Card overflows on small phones | Missing viewport meta tag or fixed px widths |
Add <meta name="viewport"...> and switch to rem + media queries |
| Remixicon icons don't appear | Wrong CDN URL or offline environment | Double-check the CDN link or download the library locally from remixicon.com |
| Hover effect doesn't work on mobile | :hover doesn't fire on touchscreens |
Add tabindex="0" to the card in HTML and add :focus alongside :hover in CSS |
How to Customize the Card for Your Own Projects
The fastest way to make this card yours is to change the design tokens in
_variables.scss
— one file, everything updates. Here's a quick reference:
| What to change | Variable | Example |
|---|---|---|
| Card gradient color | --gradient-color |
Change the HSL hue values (e.g., 230° for blue) |
| Font family | --body-font |
Replace 'Poppins' with any Google Font |
| Page background color | --body-color |
Any HSL or hex value |
| Card width | .card { width } |
Try 260px–340px for most use cases |
Want to display multiple cards on one page? Wrap the cards in a grid container:
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
padding: 2rem;
}
This single CSS rule creates a responsive grid that automatically adjusts the number of columns based on the available viewport width — no media queries needed for the grid itself.
For deeper inspiration on building more advanced HTML/CSS components, check out our guide on how to learn programming languages effectively — it includes a structured roadmap for front-end development.
Final Result
Once you've pulled the full source code from the GitHub repository, your result should look like the animation below. Notice how the info panel smoothly slides up on hover, and how the card maintains its proportions across screen sizes:
Final result: the responsive profile card with slide-up hover animation.
Conclusion
Building a responsive profile card with HTML and CSS is one of those projects that teaches you an outsized number of concepts for its size. In this guide, you covered:
- Organizing a front-end project with the SCSS Partials pattern
- Using CSS custom properties to create a maintainable design system
- Writing a pure-CSS hover animation with zero JavaScript
- Making a component truly responsive with
remunits and media queries - Supporting touch devices with the
:focuspseudo-class - Avoiding the five most common beginner CSS mistakes
The real value isn't the card itself — it's the mental model you now have for building any CSS component: define your tokens, write the base reset, style the component, layer in the interaction, then test responsiveness. Repeat that pattern and you'll be building production-ready UIs faster than you think.
If you want to keep pushing your front-end skills, have a look at our Python and SQLite beginner tutorial — understanding how front-end connects to a database is the natural next step once your UI skills are solid.
Found this guide helpful?
Join hundreds of subscribers and get the latest tutorials and tech articles delivered straight to your inbox.
Yes, subscribe me! ✉️🔒 No spam, ever. Unsubscribe at any time.
We'd love to hear your thoughts! Leave a comment below
and share your experience or questions.