About Section HTML CSS JS with Image Carousel

This About section HTML CSS JS tutorial shows you how to build a two-column header layout with a large heading, a description, and a custom image carousel below it. The carousel uses vanilla JavaScript to cycle through slides with a crossfade animation, a progress bar, and a dynamic caption that updates with each slide. No libraries needed.

Final output:

About 01

A small practice built on curiosity, craft, and long-term thinking.

We partner with people and brands we genuinely believe in, creating work that holds up over time and speaks clearly without effort.

Our story


Steps for Responsive About Section HTML CSS JS with Image Carousel:

1. Link the Hanken Grotesk font from Google Fonts in the head.

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">

Let’s start with a section element with the class about-section. Inside it, add an .about-header div.

<section class="about-section">
    <div class="about-header"></div>
</section>

Then reset margins, set Hanken Grotesk as the font family, and give the section a warm off-white background with a muted text color. Use clamp() on the padding so it scales smoothly with the screen size.

* {
    margin: 0;
    box-sizing: border-box;
}
body {
    font-family: "Hanken Grotesk", sans-serif;
}
.about-section {
    background-color: hsl(45, 15%, 95%);
    color: hsl(60, 2%, 47%);
    padding: clamp(2rem, 0.72rem + 4.7vw, 5rem) clamp(1.5rem, 0.32rem + 4.5vw, 6rem);
}
.about-section .about-header {
    max-width: 75rem;
    margin-inline: auto;
}

2. Add the top label row inside .about-header. It has three spans — the “About” label, a .hr span that acts as a horizontal rule, and the section number.

<div class="about-header">
    <div>
        <span>About</span>
        <span class="hr"></span>
        <span>01</span>
    </div>
</div>

Use flexbox with the align-items property set to center on this row. Set the flex property to 1 on .hr so it stretches to fill all the space between the label and the number. Style both label spans with a small uppercase font and wide letter spacing.

.about-section .about-header > div {
    display: flex;
    align-items: center;
    gap: 1.25rem;
}
.about-section .about-header > div span {
    font-weight: 500;
    font-size: .675rem;
    line-height: 1.5;
    letter-spacing: 2.2px;
    text-transform: uppercase;
}
.about-section .hr {
    width: 100%;
    height: 1px;
    background-color: hsla(60, 4%, 5%, 0.11);
}

Output:

About 01

3. Add the .heading-wrapper div after the label row. Inside it, add the heading on the left and a div with the description paragraph and a link on the right.

<div class="heading-wrapper">
    <h2>A small practice built on curiosity, craft, and long-term thinking.</h2>
    <div>
        <p>We partner with people and brands we genuinely believe in, creating work that holds up over time and speaks clearly without effort.</p>
        <a href="#">
            Our story
            <svg width="12" height="12" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M2.29102 5.5H8.70768" stroke="#0E0E0D" stroke-width="0.916667" stroke-linecap="round" stroke-linejoin="round" />
                <path d="M5.5 2.29297L8.70833 5.5013L5.5 8.70964" stroke="#0E0E0D" stroke-width="0.916667" stroke-linecap="round" stroke-linejoin="round" />
            </svg>
        </a>
    </div>
</div>

Use a two-column grid on .heading-wrapper so the heading and description sit side by side. Use clamp() on the gap and top margin so they scale with the screen. On mobile, collapse to a single column so they stack vertically.

.about-section .heading-wrapper {
    display: grid;
    grid-template-columns: 1.25fr 1fr;
    gap: 1rem clamp(2rem, 0.95rem + 4.5vw, 6rem);
    margin-top: clamp(1.5rem, 0.72rem + 3.29vw, 3.5rem);
}
@media screen and (max-width: 768px) {
    .about-section .heading-wrapper {
        grid-template-columns: 1fr;
    }
}

Output:

About 01

A small practice built on curiosity, craft, and long-term thinking.

We partner with people and brands we genuinely believe in, creating work that holds up over time and speaks clearly without effort.

Our story

4. Style the heading with a light font weight and use clamp() on the font size so it scales smoothly. Set a negative letter spacing to make the large text feel tight. Style the description with a light weight and a comfortable line height.

Then style the link as an inline flex element so the arrow SVG sits on the same line as the text. Add a bottom border and an arrow translation on hover so it feels interactive.

.about-section .heading-wrapper h2 {
    font-weight: 300;
    font-size: clamp(1.5rem, 1.1rem + 1.7vw, 2.625rem);
    line-height: 1.19;
    letter-spacing: -1.05px;
    color: hsl(60, 4%, 5%);
}
.about-section .heading-wrapper p {
    font-weight: 300;
    font-size: 1rem;
    line-height: 1.6;
}
.about-section .heading-wrapper a {
    margin-top: .675rem;
    display: inline-flex;
    align-items: center;
    gap: .675rem;
    text-decoration: none;
    font-weight: 500;
    font-size: .75rem;
    line-height: 1;
    letter-spacing: 1.98px;
    text-transform: uppercase;
    color: hsl(60, 4%, 5%);
    padding-bottom: .375rem;
    border-bottom: 1px solid hsla(60, 4%, 5%, 0.3);
}
.about-section .heading-wrapper a svg {
    transition: transform .3s;
}
.about-section .heading-wrapper a:hover svg {
    transform: translateX(30%);
}

Output:

About 01

A small practice built on curiosity, craft, and long-term thinking.

We partner with people and brands we genuinely believe in, creating work that holds up over time and speaks clearly without effort.

Our story

5. Add the .about-carousel-wrapper div after .heading-wrapper. Inside it, add the .about-image-carousel div with an .images-wrapper containing three images.

Each image gets the class slide, and the first one also gets the class active so it shows on load. Add a data-caption attribute to each image since the JavaScript reads it to update the caption text.

<div class="about-carousel-wrapper">
    <div class="about-image-carousel">
        <div class="images-wrapper">
            <img class="slide active" data-caption="Studio Session — Berlin, 2024" src="about-1.jpg">
            <img class="slide" data-caption="Design Sprint — Nairobi, 2024" src="about-2.jpg">
            <img class="slide" data-caption="Client Review — Lagos, 2024" src="about-3.jpg">
        </div>
    </div>
</div>

Set the position property to relative on .images-wrapper and a fixed height using clamp() so it scales with the screen. Stack all images on top of each other using position absolute with the top and left properties set to 0. Set the opacity property to 0 on all images by default, then set it to 1 only on the .active image. Add a transition on opacity so the crossfade between slides animates smoothly.

.about-section .about-carousel-wrapper {
    margin-top: clamp(1.75rem, 0.87rem + 3.76vw, 4rem);
}
.about-section .about-image-carousel {
    width: 100%;
}
.about-section .images-wrapper {
    position: relative;
    width: 100%;
    height: clamp(15rem, calc(12.9rem + 30.2vw), 36rem);
}
.about-section .images-wrapper img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    position: absolute;
    top: 0;
    left: 0;
    opacity: 0;
    transition: opacity 0.6s ease;
}
.about-section .images-wrapper img.active {
    opacity: 1;
}

Output:

About 01

A small practice built on curiosity, craft, and long-term thinking.

We partner with people and brands we genuinely believe in, creating work that holds up over time and speaks clearly without effort.

Our story

6. Add the .controls div after .images-wrapper. Inside it, add .controls-left with the current number, a progress bar, and the total count. Add .controls-right with the caption text and the prev/next buttons.

<div class="controls">
    <div class="controls-left">
        <span id="current-number">01</span>
        <div class="bar-background">
            <div class="bar-fill" id="bar-fill"></div>
        </div>
        <span>03</span>
    </div>
    <div class="controls-right">
        <span class="caption" id="caption-text">Studio Session — Berlin, 2024</span>
        <div class="button-wrapper">
            <button id="prev-btn">
                <svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M6.50065 10.2904L2.70898 6.4987L6.50065 2.70703" stroke="#7A7A75" stroke-width="1.08333"
                        stroke-linecap="round" stroke-linejoin="round" />
                    <path d="M10.2923 6.5H2.70898" stroke="#7A7A75" stroke-width="1.08333" stroke-linecap="round"
                        stroke-linejoin="round" />
                </svg>
            </button>
            <button id="next-btn">
                <svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
                    <path d="M2.70898 6.5H10.2923" stroke="#7A7A75" stroke-width="1.08333" stroke-linecap="round"
                        stroke-linejoin="round" />
                    <path d="M6.5 2.70703L10.2917 6.4987L6.5 10.2904" stroke="#7A7A75" stroke-width="1.08333"
                        stroke-linecap="round" stroke-linejoin="round" />
                </svg>
            </button>
        </div>
    </div>
</div>

Use flexbox with the justify-content property set to space-between on .controls to push the left and right groups to opposite ends. Style .controls-left with a fixed flex basis so it doesn’t shrink.

Now style the .bar-background as a thin horizontal line, and .bar-fill inside it as the active portion that grows as you move through the slides. Style the buttons as square transparent elements with a subtle border.

.about-section .controls {
    font-weight: 500;
    font-size: .675rem;
    line-height: 1.5;
    letter-spacing: 1.5px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: .75rem;
    margin-top: 1.25rem;
}
.about-section .controls-left {
    display: flex;
    align-items: center;
    gap: .75rem;
    flex-basis: 9.125rem;
}
@media screen and (max-width: 768px) {
    .about-section .controls-left {
        flex-basis: 100%;
    }
}
.about-section #current-number {
    color: hsl(60, 4%, 5%);
}
.about-section .bar-background {
    flex-basis: 6rem;
    height: 1px;
    background: hsla(60, 4%, 5%, 0.11);
}
@media screen and (max-width: 768px) {
    .about-section .bar-background {
        flex-basis: 100%;
    }
}
.about-section .bar-fill {
    height: 100%;
    background: hsla(60, 4%, 5%, 0.5);
    width: 33%;
    transition: width 0.3s;
}
.about-section .controls-right {
    display: flex;
    align-items: center;
    gap: 1rem;
}
@media screen and (max-width: 768px) {
    .about-section .controls-right {
        justify-content: space-between;
        flex: 1;
    }
}
.about-section .caption {
    flex: 1;
    font-weight: 300;
    font-size: .675rem;
    line-height: 1.5;
    letter-spacing: 1.2px;
    text-transform: uppercase;
}
.about-section .controls-right button {
    width: 2.25rem;
    height: 2.25rem;
    border: 1px solid hsla(60, 4%, 5%, 0.11);
    background-color: transparent;
    cursor: pointer;
}
.about-section .controls-right button:hover {
    border: 1px solid hsla(60, 4%, 5%, 0.31);
    background-color: hsla(60, 4%, 5%, 0.05);
}

Output:

About 01

A small practice built on curiosity, craft, and long-term thinking.

We partner with people and brands we genuinely believe in, creating work that holds up over time and speaks clearly without effort.

Our story

7. Now add the JavaScript. Link script.js before the closing body tag. Wait for the page to load using DOMContentLoaded, then grab all the slide images, the current number element, the progress bar fill, and the caption text element. Keep track of which slide is currently showing using a currentIndex variable that starts at 0.

document.addEventListener('DOMContentLoaded', function () {
    var slides = document.querySelectorAll('.about-image-carousel .slide');
    var currentIndex = 0;
    var numberEl = document.querySelector('.about-image-carousel #current-number');
    var barFillEl = document.querySelector('.about-image-carousel #bar-fill');
    var captionEl = document.querySelector('.about-image-carousel #caption-text');
});

Define the updateSlide function. It first removes the active class from all images, then adds it back to only the current one so it fades in. It also updates the slide counter to show “01”, “02”, or “03” instead of “0”, “1”, “2”. Then it calculates the bar fill percentage based on how far through the slides the user is, and finally reads the caption straight from the active image’s data-caption attribute.

function updateSlide() {
    for (var i = 0; i < slides.length; i++) {
        slides[i].classList.remove('active');
    }
    slides[currentIndex].classList.add('active');
    numberEl.textContent = "0" + (currentIndex + 1);
    var percent = ((currentIndex + 1) / slides.length) * 100;
    barFillEl.style.width = percent + "%";
    captionEl.textContent = slides[currentIndex].dataset.caption;
}

Finally, attach click listeners to the prev and next buttons. When the next button is clicked, increase currentIndex by one. If it goes past the last slide, reset it back to 0 so it loops to the beginning. When the prev button is clicked, decrease currentIndex by one. If it goes below 0, set it to the last slide index so it loops to the end. After each click, call updateSlide to apply the changes.

document.getElementById('next-btn').addEventListener('click', function () {
    currentIndex = currentIndex + 1;
    if (currentIndex > slides.length - 1) {
        currentIndex = 0;
    }
    updateSlide();
});

document.getElementById('prev-btn').addEventListener('click', function () {
    currentIndex = currentIndex - 1;
    if (currentIndex < 0) {
        currentIndex = slides.length - 1;
    }
    updateSlide();
});

Output:

About 01

A small practice built on curiosity, craft, and long-term thinking.

We partner with people and brands we genuinely believe in, creating work that holds up over time and speaks clearly without effort.

Our story


Final Output Code for Responsive About Section HTML CSS JS with Image Carousel:

HTML

<section class="about-section">
    <div class="about-header">
        <div>
            <span>About</span>
            <span class="hr"></span>
            <span>01</span>
        </div>
        <div class="heading-wrapper">
            <h2>A small practice built on curiosity, craft, and long-term thinking.</h2>
            <div>
                <p>We partner with people and brands we genuinely believe in, creating work that holds up over time and
                    speaks clearly without effort.</p>
                <a href="#">
                    Our story
                    <svg width="12" height="12" viewBox="0 0 11 11" fill="none" xmlns="http://www.w3.org/2000/svg">
                        <path d="M2.29102 5.5H8.70768" stroke="#0E0E0D" stroke-width="0.916667" stroke-linecap="round"
                            stroke-linejoin="round" />
                        <path d="M5.5 2.29297L8.70833 5.5013L5.5 8.70964" stroke="#0E0E0D" stroke-width="0.916667"
                            stroke-linecap="round" stroke-linejoin="round" />
                    </svg>
                </a>
            </div>
        </div>
        <div class="about-carousel-wrapper">
            <div class="about-image-carousel">
                <div class="images-wrapper">
                    <img class="slide active" data-caption="Studio Session — Berlin, 2024"
                        src="about-1.avif">
                    <img class="slide" data-caption="Design Sprint — Nairobi, 2024"
                        src="about-2.avif">
                    <img class="slide" data-caption="Client Review — Lagos, 2024"
                        src="about-3.avif">
                </div>
                <div class="controls">
                    <div class="controls-left">
                        <span id="current-number">01</span>
                        <div class="bar-background">
                            <div class="bar-fill" id="bar-fill"></div>
                        </div>
                        <span>03</span>
                    </div>
                    <div class="controls-right">
                        <span class="caption" id="caption-text">Studio Session — Berlin, 2024</span>
                        <div class="button-wrapper">
                            <button id="prev-btn">
                                <svg width="13" height="13" viewBox="0 0 13 13" fill="none"
                                    xmlns="http://www.w3.org/2000/svg">
                                    <path d="M6.50065 10.2904L2.70898 6.4987L6.50065 2.70703" stroke="#7A7A75"
                                        stroke-width="1.08333" stroke-linecap="round" stroke-linejoin="round" />
                                    <path d="M10.2923 6.5H2.70898" stroke="#7A7A75" stroke-width="1.08333"
                                        stroke-linecap="round" stroke-linejoin="round" />
                                </svg>
                            </button>
                            <button id="next-btn">
                                <svg width="13" height="13" viewBox="0 0 13 13" fill="none"
                                    xmlns="http://www.w3.org/2000/svg">
                                    <path d="M2.70898 6.5H10.2923" stroke="#7A7A75" stroke-width="1.08333"
                                        stroke-linecap="round" stroke-linejoin="round" />
                                    <path d="M6.5 2.70703L10.2917 6.4987L6.5 10.2904" stroke="#7A7A75"
                                        stroke-width="1.08333" stroke-linecap="round" stroke-linejoin="round" />
                                </svg>
                            </button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</section>

CSS

* {
    margin: 0;
    box-sizing: border-box;
}
body {
    font-family: "Hanken Grotesk", sans-serif;
}
.about-section {
    background-color: hsl(45, 15%, 95%);
    color: hsl(60, 2%, 47%);
    padding: clamp(2rem, 0.72rem + 4.7vw, 5rem) clamp(1.5rem, 0.32rem + 4.5vw, 6rem);
}
.about-section .about-header {
    max-width: 75rem;
    margin-inline: auto;
}
.about-section .about-header > div {
    display: flex;
    align-items: center;
    gap: 1.25rem;
}
.about-section .about-header > div span {
    font-weight: 500;
    font-size: .675rem;
    line-height: 1.5;
    letter-spacing: 2.2px;
    text-transform: uppercase;
}
.about-section .hr {
    width: 100%;
    height: 1px;
    background-color: hsla(60, 4%, 5%, 0.11);
}
.about-section .heading-wrapper {
    display: grid;
    grid-template-columns: 1.25fr 1fr;
    gap: 1rem clamp(2rem, 0.95rem + 4.5vw, 6rem);
    margin-top: clamp(1.5rem, 0.72rem + 3.29vw, 3.5rem);
}
@media screen and (max-width: 768px) {
    .about-section .heading-wrapper {
        grid-template-columns: 1fr;
    }
}
.about-section .heading-wrapper h2 {
    font-weight: 300;
    font-size: clamp(1.5rem, 1.1rem + 1.7vw, 2.625rem);
    line-height: 1.19;
    letter-spacing: -1.05px;
    color: hsl(60, 4%, 5%);
}
.about-section .heading-wrapper p {
    font-weight: 300;
    font-size: 1rem;
    line-height: 1.6;
}
.about-section .heading-wrapper a {
    margin-top: .675rem;
    display: inline-flex;
    align-items: center;
    gap: .675rem;
    text-decoration: none;
    font-weight: 500;
    font-size: .75rem;
    line-height: 1;
    letter-spacing: 1.98px;
    text-transform: uppercase;
    color: hsl(60, 4%, 5%);
    padding-bottom: .375rem;
    border-bottom: 1px solid hsla(60, 4%, 5%, 0.3);
}
.about-section .heading-wrapper a svg {
    transition: transform .3s;
}
.about-section .heading-wrapper a:hover svg {
    transform: translateX(30%);
}
.about-section .about-carousel-wrapper {
    margin-top: clamp(1.75rem, 0.87rem + 3.76vw, 4rem);
}
.about-section .about-image-carousel {
    width: 100%;
}
.about-section .images-wrapper {
    position: relative;
    width: 100%;
    height: clamp(15rem, calc(12.9rem + 30.2vw), 36rem);
}
.about-section .images-wrapper img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    position: absolute;
    top: 0;
    left: 0;
    opacity: 0;
    transition: opacity 0.6s ease;
}
.about-section .images-wrapper img.active {
    opacity: 1;
}
.about-section .controls {
    font-weight: 500;
    font-size: .675rem;
    line-height: 1.5;
    letter-spacing: 1.5px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: .75rem;
    margin-top: 1.25rem;
}
.about-section .controls-left {
    display: flex;
    align-items: center;
    gap: .75rem;
    flex-basis: 9.125rem;
}
@media screen and (max-width: 768px) {
    .about-section .controls-left {
        flex-basis: 100%;
    }
}
.about-section #current-number {
    color: hsl(60, 4%, 5%);
}
.about-section .bar-background {
    flex-basis: 6rem;
    height: 1px;
    background: hsla(60, 4%, 5%, 0.11);
}
@media screen and (max-width: 768px) {
    .about-section .bar-background {
        flex-basis: 100%;
    }
}
.about-section .bar-fill {
    height: 100%;
    background: hsla(60, 4%, 5%, 0.5);
    width: 33%;
    transition: width 0.3s;
}
.about-section .controls-right {
    display: flex;
    align-items: center;
    gap: 1rem;
}
@media screen and (max-width: 768px) {
    .about-section .controls-right {
        justify-content: space-between;
        flex: 1;
    }
}
.about-section .caption {
    flex: 1;
    font-weight: 300;
    font-size: .675rem;
    line-height: 1.5;
    letter-spacing: 1.2px;
    text-transform: uppercase;
}
.about-section .controls-right button {
    width: 2.25rem;
    height: 2.25rem;
    border: 1px solid hsla(60, 4%, 5%, 0.11);
    background-color: transparent;
    cursor: pointer;
}
.about-section .controls-right button:hover {
    border: 1px solid hsla(60, 4%, 5%, 0.31);
    background-color: hsla(60, 4%, 5%, 0.05);
}

JS:

document.addEventListener('DOMContentLoaded', function () {

    var slides = document.querySelectorAll('.about-image-carousel .slide');
    var currentIndex = 0;
    var numberEl = document.querySelector('.about-image-carousel #current-number');
    var barFillEl = document.querySelector('.about-image-carousel #bar-fill');
    var captionEl = document.querySelector('.about-image-carousel #caption-text');

    function updateSlide() {
        for (var i = 0; i < slides.length; i++) {
            slides[i].classList.remove('active');
        }
        slides[currentIndex].classList.add('active');
        numberEl.textContent = "0" + (currentIndex + 1);
        var percent = ((currentIndex + 1) / slides.length) * 100;
        barFillEl.style.width = percent + "%";
        captionEl.textContent = slides[currentIndex].dataset.caption;
    }

    document.getElementById('next-btn').addEventListener('click', function () {
        currentIndex = currentIndex + 1;
        if (currentIndex > slides.length - 1) {
            currentIndex = 0;
        }
        updateSlide();
    });

    document.getElementById('prev-btn').addEventListener('click', function () {
        currentIndex = currentIndex - 1;
        if (currentIndex < 0) {
            currentIndex = slides.length - 1;
        }
        updateSlide();
    });

});

If you have any doubts or stuck somewhere, you can reach out through Coding Yaar's Discord server.

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Most Voted
Newest Oldest
0
Would love your thoughts, please comment.x
()
x