<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ jabo Landry - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ jabo Landry - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 01 Sep 2026 23:46:03 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/Arnold-Jabo/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Automate Your Tests in Express Using Vitest ]]>
                </title>
                <description>
                    <![CDATA[ Thinking through API logic while constantly switching tabs to test application integration can be overwhelming and time-consuming. Well, you can save your time and energy by writing tests for your app ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-automate-your-tests-in-express-using-vitest/</link>
                <guid isPermaLink="false">6a91aa2c85465e213741c4de</guid>
                
                    <category>
                        <![CDATA[ vitest ]]>
                    </category>
                
                    <category>
                        <![CDATA[ unit testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Integration Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mocking ai ]]>
                    </category>
                
                    <category>
                        <![CDATA[ API mocking ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mocking api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mongoose-mocking ]]>
                    </category>
                
                    <category>
                        <![CDATA[ qa testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Quality Assurance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Supertest ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mockingoose ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ jabo Landry ]]>
                </dc:creator>
                <pubDate>Fri, 28 Aug 2026 15:33:00 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/098eaca4-3334-4a81-b6f6-ad0b167eee44.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Thinking through API logic while constantly switching tabs to test application integration can be overwhelming and time-consuming.</p>
<p>Well, you can save your time and energy by writing tests for your application that run whenever you add a new feature, all without leaving your IDE during development. This will help you be confident that each feature works as expected.</p>
<p>In this guide, I'll help you build confidence through code: you'll learn how to validate APIs with tests first, then confirm the results in Postman or any other API testing tool.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p><strong>Node.js &amp; Express basics:</strong> You should have a working knowledge of Node.js and Express (or a similar library like <code>fastify</code>), including how to build and run a simple API.</p>
</li>
<li><p><strong>Working knowledge of TypeScript:</strong> The code snippets in this guide are written using TypeScript, so you should have a solid understanding of TypeScript basics.</p>
</li>
<li><p><strong>Basic familiarity with MongoDB:</strong> Helpful but not required. The examples in this guide use MongoDB for demonstration purposes, but the underlying logic applies to any database or data layer. Only the tooling differs.</p>
</li>
<li><p><strong>Hands-on API experience:</strong> Prior experience writing at least one backend API with Express will help you follow along more effectively.</p>
</li>
<li><p><strong>Curiosity and motivation:</strong> A willingness to deepen your backend skills by learning how to write and run tests for your APIs.</p>
</li>
<li><p><strong>Environment setup:</strong> Node.js version <strong>20 or higher</strong> installed on your machine.</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-testing-concept">Testing Concept</a></p>
</li>
<li><p><a href="#heading-unit-testing">Unit testing</a></p>
</li>
<li><p><a href="#heading-comparison-between-unit-and-integration-tests">Comparison between unit and integration tests</a></p>
</li>
<li><p><a href="#heading-summary">Summary</a></p>
</li>
</ul>
<p>You can find all the code snippets used in this guide in this <a href="https://github.com/jabo-arnold-landry/testing-lesson">Git repository</a>. Each example has its own branch, and they're combined into a main branch if you want a full example version. Please consider starring the repository if you find it helpful.</p>
<h2 id="heading-key-testing-concepts">Key Testing Concepts</h2>
<p>Testing helps you build confidence in your codebase. You wrie code that tests other code in your application, or code that checks if a function or feature behaves the way that it should.</p>
<p>In this guide, we'll cover two types of testing:</p>
<ul>
<li><p><strong>Unit testing</strong>: This is the most basic type of testing and, in my opinion, the easiest. You test a single piece of code in isolation to see how it behaves.</p>
</li>
<li><p><strong>Integration testing</strong>: This approach is used to test how different parts of the application are integrated to make sure they're working together as intended.</p>
</li>
</ul>
<h3 id="heading-key-testing-terms">Key Testing Terms</h3>
<p>Throughout this guide, I'll be using some technical terms are related to testing which I want to explain up front:</p>
<ul>
<li><p><strong>Mocking</strong>: mocking is a technique used to make a fake implementation of a real function call.</p>
</li>
<li><p><strong>Spying</strong>: spying is a way of inspecting a function to see, for example, if the function is called with a certain type of argument or how many times it's been called.</p>
</li>
<li><p><strong>Assertion</strong>: assertions check to see if the output you're getting matches the expected output.</p>
</li>
</ul>
<p>Alright now that we have that covered, let's go over some basics of testing so you have the best practices down before we start writing tests.</p>
<h3 id="heading-how-to-name-a-test-file">How to Name a Test File</h3>
<p>Make sure your test files follow one of these naming conventions:</p>
<ul>
<li><p><code>filename.test.ts</code></p>
</li>
<li><p><code>filename.spec.ts</code></p>
</li>
<li><p><code>filename.test.js</code></p>
</li>
<li><p><code>filename.spec.js</code></p>
</li>
</ul>
<p>Both the <code>spec</code> and <code>test</code> keyword in a file name makes it possible to run the test file. They also help testing frameworks run the right file.</p>
<p>You can choose either the TypeScript or JavaScript extension on a file based on which language you're using to write the test. For this guide I'm using TypeScript so I'll be using the TypeScript (<code>.ts</code>) test file version.</p>
<h3 id="heading-parts-of-a-testing-file">Parts of a Testing File</h3>
<p>Typically, a test file will have four main parts that you should be familiar with, which are:</p>
<ul>
<li><p><code>describe</code>: Used to describe which test you're going to write.</p>
</li>
<li><p><code>it</code>: Used to specify a condition that a function must pass when tested against.</p>
</li>
<li><p><code>expect</code>: Used to determine what type of results you're expecting when you call a function that's being tested</p>
</li>
<li><p><code>matchers</code>: These are different method available on the <code>expect</code> keyword that we use to evaluate if the function we're testing returns a value that meets the expected data or value.</p>
</li>
</ul>
<p>Example of a test file:</p>
<pre><code class="language-typescript">import { describe, it, expect } from "vitest";
import validateEmail from "../utils/email-validation";

describe("email validation test suites", () =&gt; {
  it("must define email validation function", () =&gt; {
    expect(validateEmail).toBeDefined();
  });
});
</code></pre>
<p>The above snippet tests if the <code>validateEmail</code> function is defined.</p>
<p>In this test file:</p>
<ul>
<li><p>We use <code>describe</code> to specify the description of the test. <code>describe</code> receives a string description of the test and a function to handle different test cases.</p>
</li>
<li><p>The <code>it</code> keyword specifies and defines a test for a function you're testing, <code>it</code> receives a string describing a specific test description and a callback function to execute and handle test assertion.</p>
</li>
<li><p>Then <code>expect</code> uses the function return type to check if it matches a specific condition through the <code>toBeDefined</code> matcher method.</p>
</li>
</ul>
<h3 id="heading-list-of-matchers">List of Matchers:</h3>
<p>There are many matchers available to you. Below are a few of them:</p>
<ul>
<li><p><code>toBe</code>: compares the passed-in value to see if it matches the function's returned value. It's used on primitive data types like strings, numbers, and so on.</p>
</li>
<li><p><code>toEqual</code>: compares the passed-in value to see if it matches the function's returned value. It's used on non-primitive data types like objects, arrays, and so on.</p>
</li>
<li><p><code>toThrow</code>: used on a function that threw an error to check if the function threw expected error object or instance.</p>
</li>
<li><p><code>toBeCalledWith</code>: used to check if a function is called with a given parameter.</p>
</li>
<li><p><code>toBecalledOnce</code>: used to check if a function is called only once.</p>
</li>
<li><p><code>toBeDefined</code>: used to check if a function is defined.</p>
</li>
<li><p><code>toBeUndefined</code>: used to check if a function returns an undefined value.</p>
</li>
<li><p><code>toBeTruthy</code>: used to check if a function returns a true Boolean value.</p>
</li>
<li><p><code>toBeFalsy</code>: used to check if a function returns a false Boolean value.</p>
</li>
</ul>
<p>These are few of the many matchers out there.</p>
<h3 id="heading-vitest-installation">Vitest Installation</h3>
<p>Now that you know some testing basics, we can get into the actual tests. We'll start by installing <code>vitest</code>, the framework that we'll use to run and write tests for our application.</p>
<p>You can choose your preferred package manager to use to install <code>vitest</code> from the list below:</p>
<pre><code class="language-shell">pnpm add -D vitest # for pnpm package manager
npm install -D vitest # for npm package manager
yarn add -D vitest # for yarn package manager
bun add -D vitest # for bun package manager
</code></pre>
<h2 id="heading-unit-testing">Unit Testing</h2>
<p>A unit test focuses on testing a small piece of code in isolation in your application. A simple example could be if you have a function that adds contact info to a database. For the test, you could check if the email is valid before adding the contact to the database.</p>
<p>Let's start by testing a simple email validation function so you can get comfortable with how unit tests works and how to write one:</p>
<pre><code class="language-typescript">export default function validateEmail(email: string) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

  if (regex.test(email)) {
    return true;
  } else {
    throw new Error("Invalid email format");
  }
}
</code></pre>
<p>The above snippet exports a function that receives an email and then uses regex to validate if the email is valid. It throws an error if the email is invalid.</p>
<h3 id="heading-tests-for-the-validateemail-function">Tests for the <code>validateEmail</code> Function</h3>
<p>Let's start by checking if <code>validateEmail</code> returns true for correct emails:</p>
<pre><code class="language-typescript">import { describe, it, expect } from "vitest";
import validateEmail from "../utils/email-validation";

describe("email validation test suites", () =&gt; {

  it("returns true for valid email", () =&gt; {
    const sampleEmail = "arnoldjabo@gmail.com";
    expect(validateEmail(sampleEmail)).toBeTruthy();
  });

});
</code></pre>
<p>In the above test, we're creating a variable <code>sampleEmail</code> to be used as a sample email in the <code>validateEmail</code> function. Save this and then run <code>npx vitest</code> in your terminal. You should see a terminal with the results of your test. It should look like the below screenshot:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69c7bcff7cf27065100ae8be/84af7e2b-8344-49c8-a744-35d64ee2a1c0.png" alt="passed tests in vitest terminal" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>When you run tests, Vitest shows the list of test files you're testing, how many tests were executed, and how many passed and failed tests you have.</p>
<p>Let's create another test that detects an invalid email:</p>
<pre><code class="language-typescript">import { describe, it, expect } from "vitest";
import validateEmail from "../utils/email-validation";

describe("email validation test suites", () =&gt; {

    it("throws error for invalid email", () =&gt; {
    const sampleEmail = "verymasd.com";
    const invalidEmailResults = () =&gt; validateEmail(sampleEmail);
    expect(invalidEmailResults).toThrow("Invalid email format");
  });

});
</code></pre>
<p>For functions that throw errors, you need to wrap them inside another function to prevent them from stopping the test before the test reaches the assertion or <code>expect</code> section.</p>
<p>In our example above, the <code>validateEmail</code> function is wrapped inside another function which will hold whatever the error <code>validateEmail</code> throws is. It then assigns it to the <code>invalidEmailResults</code> variable. Next we use the <code>toThrow</code> matcher on the <code>expect</code> assertion to match a type of error <code>validateEmail</code> expects to be thrown for an invalid email.</p>
<p>If your run the test, you'll have two passed tests now:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69c7bcff7cf27065100ae8be/521702d7-073b-44e7-90dc-9a30b664b81d.png" alt="Passed test for invalid email that throws an error for an invalid email" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>If you didn't wrap the <code>validateEmail</code> function inside another function when it throws an error, you'll see something like this when you run the test:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69c7bcff7cf27065100ae8be/693fec20-7f56-42ea-be18-4caa5cba3dac.png" alt="The error message you would receive inside the test terminal if you didn't wrap a function that throws an error inside another function." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>As you can see, the error fires before reaching the test final assertion. So when you have a function that throws errors, remember to wrap your function inside another function to avoid throwing errors mid-test.</p>
<p><strong>Tip</strong>: When writing unit tests, focus on the expected input and output of a function. You don't have to worry or need to think about the implementation of the function: the input and output are the key elements here.</p>
<h3 id="heading-testing-api-calls">Testing API Calls</h3>
<p>By now you should understand how a unit test works. So next, let's see how you can unit test an API that makes a call to a database.</p>
<pre><code class="language-typescript">import Contacts from "../../schema/contactList";
import { Request, Response } from "express";
import validateEmail from "../../utils/email-validation";

async function addContacts(req: Request, res: Response) {
  const { contactName, phoneNumber, email } = req.body;
  validateEmail(email);

  const contact = await Contacts.create({ contactName, phoneNumber, email });

  return res.status(201).json({ message: `successfully created ${contact.contactName}` });
}

export default addContacts;
</code></pre>
<p>In the code snippet above, we have a function that creates a contact with the database. It also validates if the passed email is valid.</p>
<p>In this example, we're using mongo DB for the database and Mongoose for connecting the codebase with our mongo DB instance.</p>
<p>Unit tests for non-pure functions (that is, functions that are dependent on external services, like making API calls or calling other functions) are tested a bit differently.</p>
<p>For these cases, you create a mock or fake version of the original function that makes a call to the external service and then define its behavior to match the expected return value that you'd have if you used its real version.</p>
<p>We'll start by mocking the implementation of the email validation function. This will help solidify the understanding on how mocking works in unit testing and testing in general.</p>
<p><strong>Note</strong>: mocking the <code>validateEmail</code> function isn't that important because it doesn't make a big difference from using the email validation function directly here. But for learning purposes, we'll mock it to help you understand how it works.</p>
<p>When mocking the modules import, we use the <code>vi.mock</code> function which helps transform the imports of a given module into mocks or fake versions of the real ones.</p>
<pre><code class="language-typescript">vi.mock(filepath,callback);
</code></pre>
<p><code>vi.mock</code> receives two arguments: the file path location of the module you want to mock, and a callback function called a factory function which we'll use to transform the module imports into mocks.</p>
<p>Let's start by mocking the <code>validateEmail</code> path and creating a factory function to transform the module into mocks:</p>
<pre><code class="language-typescript">vi.mock("../utils/email-validation", () =&gt; {
  return { default: vi.fn()};
});
import validateEmail from "../utils/email-validation";
</code></pre>
<p>In the callback function (factory function), we then return an object of the exported module, with a key of <strong>default</strong> and a value of <code>vi.fn</code>. For function mocking we use <code>vi.fn()</code> which automatically replaces the function's return value with <code>undefined</code>.</p>
<p>We use default as a key because the <code>validateEmail</code> function is exported as a default export. If it was a named export, we would have used the actual export name instead of default in the return object.</p>
<pre><code class="language-typescript">vi.mock("../utils/email-validation", () =&gt; {
  return { validateEmail: vi.fn() };
});
import { validateEmail } from "../utils/email-validation";
</code></pre>
<p>Always import your module after the mock module operation to avoid using the real module.</p>
<p>There are methods on <code>vi.fn()</code> that help define the implementation and behaviors of the mocked function. Some of these methods include:</p>
<ul>
<li><p><code>mockReturnValue</code>: Used to define a return value for a mocked function</p>
</li>
<li><p><code>mockRejectsValue</code>: Used for promise-based functions to define the error the function will return.</p>
</li>
<li><p><code>mockResolveValue</code>: Used for promise-based functions to define the data the function will return.</p>
</li>
<li><p><code>mockImplementation</code>: Used to define a new function behavior of a mocked function.</p>
</li>
<li><p><code>mockReturnThis</code>: Used to return the actual instance of a function you're mocking.</p>
</li>
</ul>
<p>These are the methods that you'll likely use most of the time when defining mock implementation and setting mock return value. Just keep in mind that there are many others.</p>
<p>Here, for <code>emailValidate</code>, we'll be using the <code>mockReturnValue</code> and <code>mockImplementation</code> methods.</p>
<p>Let's use <code>mockReturnValue</code> to make the validateEmail function return true by default, assuming the email will be formatted correctly:</p>
<pre><code class="language-typescript">vi.mock("../utils/email-validation", () =&gt; {
  return { default: vi.fn().mockReturnValue(true) };
});
import validateEmail from "../utils/email-validation";
</code></pre>
<p>We define a mock function with <code>vi.fn</code> and then chain on the <code>mockReturnValue(true)</code> to change the mock function default return value (undefined) to true in our case.</p>
<h3 id="heading-defining-the-mocking-implementation">Defining the Mocking Implementation</h3>
<p>You can do a lot with a mocked function, like defining a new implementation for the mocked function that replaces existing logic in the original function.</p>
<p>Let's create a test suite with a fake email that throws an error when you pass the wrong email while creating contacts.</p>
<pre><code class="language-typescript">import { describe, expect, it, vi } from "vitest";
import mockinggoose from "mockingoose";
import Contacts from "../schema/contactList";
import addContacts from "../src/controllers/add-contacts.controller";

import { type Response, type Request } from "express";

vi.mock("../utils/email-validation", () =&gt; {
  return { default: vi.fn().mockReturnValue(true) };
});

import validateEmail from "../utils/email-validation";

const fakeContact = {
  contactName: "arnold",
  phoneNumber: 798600102,
  email: "arnoldjabo@gmail.com",
};

describe("Add contacts to the database", async () =&gt; {
  it("throws error for the wrong email address", async () =&gt; {
    const req = {
      body: { ...fakeContact, email: "fakemail" },
    } as Request;  

    const res = {
      status: vi.fn().mockReturnThis(),
      json: vi.fn(),
    } as any as Response;

    (validateEmail as ReturnType&lt;typeof vi.fn&gt;).mockImplementation(() =&gt; {
      throw new Error("invalid email!");
    });
 
    await expect(addContacts(req, res)).rejects.toThrow();
  });
});
</code></pre>
<p>In the snippet above, we're mocking or creating a fake request object that's cast as request type of <code>express</code>. We do the same with the response object – but the difference here is that with response we're also creating mocks for common methods that you'd use on an Express response (which are status and a <code>json</code> object).</p>
<p>We then turn the return type of the <code>validateEmail</code> function into the vitest mocking function type to avoid TypeScript warnings. Then we use the <code>mockImplementation</code> method to throw a new error inside <code>validateEmail</code>.</p>
<p>The assertion works differently because now we're throwing a promise-based error. We use <code>rejects</code> on the assertion and then chain on another matcher that stimulates which type of error the function will throw.</p>
<p><strong>Tip:</strong> when working with TypeScript, the response object can't be cast like we did on the request object because the response object is much stricter than request. So you'll first need to cast it as any and then cast back to the response object. That way you avoid the TypeScript warning while still keeping the type in play for your test code.</p>
<h3 id="heading-mocking-a-mongoose-model">Mocking a Mongoose Model</h3>
<p>With unit tests, we don't want to save test data to a real database. Instead we can fake the implementation of the service that calls the database service – in our case, we can use the <code>create</code> method from Mongoose. It'll save the record to a Mongo database. We can then define what it should return on success (and it should look identical to what it would return if we were using a real database).</p>
<p>We'll start by installing a library for mocking a Mongoose model called <code>mockingoose</code>:</p>
<pre><code class="language-shell">pnpm add -D mockingoose # for pnpm package manager
npm install -D mockingoose # for npm package manager
yarn add -D mockingoose # for yarn package manager
bun add -D mockingoose # for bun package manager
</code></pre>
<p>After installation we'll create a mock for our contacts model:</p>
<pre><code class="language-typescript">import { describe, expect, it, vi } from "vitest";
import mockinggoose from "mockingoose";
import Contacts from "../schema/contactList";

const fakeContact = {
  contactName: "arnold",
  phoneNumber: 798600102,
  email: "arnoldjabo@gmail.com",
};

describe("Add contacts to the database", async () =&gt; {
  it("successfully create a new contact to the database", async () =&gt; {
    mockinggoose(Contacts).toReturn(fakeContact, "save");
  });

});
</code></pre>
<p>To mock a Mongoose model, we call the <code>mockinggoose()</code> function and pass the model to mock. Then we use the <code>toReturn</code> matcher to describe what it should return, <code>toReturn</code> matcher expects two arguments.</p>
<p>Those arguments are a fake dataset for the model and a Mongo method that we'll use to work with the data. For our example we'll use <code>save</code> because we're creating records in the document.</p>
<h3 id="heading-unit-testing-the-api">Unit Testing the API</h3>
<p>We can start by writing the first test for the add contact API call like this:</p>
<pre><code class="language-typescript">import { describe, expect, it, vi } from "vitest";
import mockinggoose from "mockingoose";
import Contacts from "../schema/contactList";
import addContacts from "../src/controllers/add-contacts.controller";
import { type Response, type Request } from "express";

vi.mock("../utils/email-validation", () =&gt; {
  return { default: vi.fn().mockReturnValue(true) };
});

import validateEmail from "../utils/email-validation";

const fakeContact = {
  contactName: "arnold",
  phoneNumber: 798600102,
  email: "arnoldjabo@gmail.com",
};

describe("Add contacts to the database", async () =&gt; {
  it("successfully create a new contact to the database", async () =&gt; {
    mockinggoose(Contacts).toReturn(fakeContact, "save");

    const req = {
      body: fakeContact,
    } as Request;

    const res = {
      status: vi.fn().mockReturnThis(),
      json: vi.fn(),
    } as any as Response;

    await addContacts(req, res);
    expect(res.status).toHaveBeenCalledWith(201);
    expect(res.json).toHaveBeenCalledWith({
      message: `successfully created ${fakeContact.contactName}`,
    });
  });
 });
</code></pre>
<p>For this test assertion, we're using different matchers called <strong>spies</strong>. These are used on a function to inspect how many times it's been called or which parameters were used to call it (and so on).</p>
<p>Here we're expecting the status function of response to be called with a status of 201 as its argument. Then the JSON object is called with a message argument that we're using to send out the response.</p>
<h2 id="heading-integration-tests">Integration Tests</h2>
<p>Integration tests test the communication and integration of different parts of an application. For example, they might check if your database integrates well with the function that makes the API call to the database.</p>
<p>Unlike unit tests (where we don't need to have our test making API calls), with integration tests we're testing if parts of the application integrate together and works as expected. We don't need to mock anything, because we want to make sure that we're successfully sending a request to the backend and connecting to the database.</p>
<h3 id="heading-creating-integration-test-data-storage">Creating Integration Test Data Storage</h3>
<p>When you're running integration tests, there are two ways to create a testing environment that acts as a database. They include:</p>
<ul>
<li><p>Creating a duplicate schema of your real database and using the copy as a testing database environment. Whenever you're running tests, you point your database connectivity to the test DB.</p>
</li>
<li><p>Creating in-memory database storage. This approach doesn't require you to have two separate schemas (one for testing and another for production). Instead you construct the same schema shape in your codebase memory and use it as your testing environment.</p>
</li>
</ul>
<p>Using the first approach is complicated because you have to set up and configure which database to use for which environment. But for the second approach, you can just set up the right schema structure as the original schema and use it for testing without needing to configure it in the database and remove it after use.</p>
<p>For Mongo DB there's a package that simplifies the in-memory storage option for us called <code>mongodb-memory-server</code>. It deletes all the data that was used for testing after the tests have finished running.</p>
<h3 id="heading-how-to-set-up-the-environment-for-the-integration-tests">How to Set Up the Environment for the Integration Tests</h3>
<p>You'll need to install:</p>
<ul>
<li><p><code>supertest</code>: a package that helps you make API calls/requests and returns back the response when testing.</p>
</li>
<li><p><code>mongodb-memory-server</code>: a package that makes in memory database storage for testing data.</p>
</li>
</ul>
<pre><code class="language-shell"># command for pnpm package manager
pnpm add -D mongodb-memory-server supertest @types/supertest

# command for npm package manager
npm install --save-dev mongodb-memory-server supertest @types/supertest

# command for yarn package manager
yarn add --dev mongodb-memory-server supertest @types/supertest

# command for bun package manager
bun add -d mongodb-memory-server supertest @types/supertest
</code></pre>
<p>Before moving on, we need to change the setup of our server entry file.</p>
<p>If you've been using Express with Node or any other framework, you may be familiar with the following type of setup for the server entry file where everything is added into a single file:</p>
<pre><code class="language-typescript">import express from "express";
import { loadEnvFile } from "node:process";
import connectToDB from "../config/dbConfig";
import addContacts from "./controllers/add-contacts.controller";

const app = express();
loadEnvFile();
async function dbConnection() {
  await connectToDB();
}
dbConnection();

app.use(express.json());
app.post("/add-contacts", addContacts);
app.listen(5000, () =&gt; console.log("the server successfully connected"));
</code></pre>
<p>This setup works fine and it's valid in certain cases. But when working with integration tests, it can be problematic. This is because in integration tests, we'll need an instance of Express to use when making the request. If we export the <code>app</code> variable here inside the main file when we make a request while testing, the production DB connection will conflict with the testing DB connection. This'll cause the tests to stop working.</p>
<p>The solution here is create another file, define an Express instance, and export it. Then we'll use the exported Express instance in the server to start a server. The setup looks like this:</p>
<p><code>app.ts</code></p>
<pre><code class="language-typescript">import express from "express";
import addContacts from "./controllers/add-contacts.controller";

const app = express()

app.use(express.json())
app.post("/add-contacts", addContacts);

export default app;
</code></pre>
<p>Then the main file <code>server.ts</code> or <code>main.ts</code> uses the <code>app</code> variable like this:</p>
<pre><code class="language-typescript">import { loadEnvFile } from "node:process";
import connectToDB from "../config/dbConfig";
import app from "./app";

loadEnvFile();

async function bootsrap() {
  await connectToDB();
  app.listen(5000, () =&gt; console.log("the server successfully connected"));
}
bootsrap();
</code></pre>
<p>We're importing the Express instance from the <code>app</code> file and then using the <code>bootstrap()</code> function to set up the database and start the server. With this in place, we can start writing integration tests for the <code>addContact</code> module.</p>
<h3 id="heading-how-to-write-the-integration-tests">How to Write the Integration Tests</h3>
<p><strong>Tip</strong>: With integration test(s) you can name your file like <code>filename.integration.test.ts</code> this is the most commonly used naming convention for integration tests, but it is not mandatory it just a naming convention.</p>
<p>You first need to set up the database testing data storage using the <code>mongoose</code> and <code>mongdb-memory-server</code> packages and the Express instance for making requests.</p>
<pre><code class="language-typescript">import { afterAll, beforeAll, describe, expect, it } from "vitest";

import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
import app from "../src/app";


describe("intergration test setup for add contact api", () =&gt; {
  let mongoServer: MongoMemoryServer;
  let server: any;

  beforeAll(async () =&gt; {
    mongoServer = await MongoMemoryServer.create();
    const uri = mongoServer.getUri();
    await mongoose.connect(uri);
    server = app.listen(0);
  });

  afterAll(async () =&gt; {
    mongoServer.stop();
    mongoose.disconnect();
    server.close();
  });
});
</code></pre>
<p>The <code>beforeAll</code> and <code>afterAll</code> functions are <code>vitest</code> functions. <code>beforeAll</code> runs before any test starts executing and <code>afterAll</code> will run after all tests are done executing.</p>
<p>In the test, we set up the database and Express instance before any test runs.</p>
<p>First, we created the <code>mongoServer</code> variable. Then, inside the <code>beforeAll</code> block, we initialize it with <code>MongoMemoryServer.create</code> to create an in-memory database for testing data storage. We get the connection string using the <code>uri</code> variable using the <code>getUri</code> method. Finally we use Mongoose to connect to the generated in-memory connection string.</p>
<p>The server variable is assigned to the Express instance listening to port 0, but you can use any port number of your choice – it's just for demonstration purposes. This creates an Express instance for our testing environment.</p>
<p>In <code>afterAll</code>, after all tests have finished executing, we close the server and in-memory DB and then also disconnect our Mongoose instance.</p>
<p>Within the same <code>describe</code> block, we then add the test description and assertion (same as we did in unit testing):</p>
<pre><code class="language-typescript">import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
import app from "../src/app";
import request from "supertest";
import Contacts from "../schema/contactList";

describe("intergration test for add contact api", () =&gt; {
  /*
    Here we do server setup and database in memory setup that was discussed,
    in the previous snippets for setting up integration data storage testing environment 
   */
  const fakeContact = {
    contactName: "arnold",
    phoneNumber: 798600102,
    email: "arnoldjabo@gmail.com",
  };

  describe("POST /add-contacts", () =&gt; {
    it("creates a new record to the database", async () =&gt; {
      const response = await request(app)
        .post("/add-contacts")
        .send(fakeContact);

       const contactList = await Contacts.findOne({
        email: "arnoldjabo@gmail.com",
      })!;
      expect(contactList?.email).toBe("arnoldjabo@gmail.com");
      console.log(contactList);

      expect(response.status).toBe(201);
      expect(response.body).toEqual({
        message: `successfully created ${fakeContact.contactName}`,
      });
    });
  });
});
</code></pre>
<p>Here in the test file, we're describing the test as a post method test for the add-contact endpoint. Then we test if it adds data to the database.</p>
<p>Within the <code>it</code> body, we use <code>request</code> from <code>supertest</code> to make a request to the server we've created. We also chain on an HTTP method with the endpoint we want to test.</p>
<p>For methods that send data to the backend like POST, PATCH, or PUT, we use the <code>send</code>() method on <code>request</code> to add an object of the data that we're sending.</p>
<p>We'll use the response to assert what the response could look like. For example, we're expecting the server to give a status code of 201 on successful data entry and a JSON object with a message property that confirms that it has added a contact.</p>
<p>We're using an assertion to check if the response's status matches what we expect, as well as if the response body matches the expected message we should be getting.</p>
<p>To test if the data are really being added to the database, I've added <code>contactList</code> to get the contact we just added by finding it by email. Then we log the <code>contactList</code> to the console to show how in-memory works. It's pretty much the same as a real Mongo DB instance. If we were to run the tests, we would have something that looks like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69c7bcff7cf27065100ae8be/e20082ae-0d06-48e2-b40b-8c66846c7ed4.png" alt="Out-put for integration test with the console log showing how that stored using in memory database looks like when printed to the screen." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>You can see from the console the in-memory stores and retrieves data as a regular Mongo database does.</p>
<h2 id="heading-when-to-use-unit-vs-integration-tests">When to Use Unit vs Integration Tests</h2>
<p>So when do you use each type of test?</p>
<p>Use unit tests when you have pure functions like the email validation example we had earlier.</p>
<p>And use Integration tests for functions that makes external API calls that are dependent on external service like database calls to avoid mocking every function that you're importing.</p>
<h2 id="heading-summary">Summary</h2>
<p>This guide explains how two types of testing work: unit tests and integration tests.</p>
<p>Unit tests are code that tests specific pieces of your codebase in isolation, and are best for pure functions. Integration test are code that tests successful integration and communication between parts of your application, and they're best for non-pure functions.</p>
<p>If you found the article helpful, you can <a href="https://buymeacoffee.com/jabo1200">buy me coffee</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create Accessible Modals and Pop-ups Using HTML, CSS, and Minimal JavaScript ]]>
                </title>
                <description>
                    <![CDATA[ Creating pop-ups and modals on your site can be a complicated process. And it often requires a lot of boilerplate code to get started. But the real challenge comes when you want to make that modal or  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-accessible-modals-and-pop-ups-using-html-css-and-minimal-javascript/</link>
                <guid isPermaLink="false">6a5a91c20842ed39785bc7c0</guid>
                
                    <category>
                        <![CDATA[ anchor css ]]>
                    </category>
                
                    <category>
                        <![CDATA[ popup ]]>
                    </category>
                
                    <category>
                        <![CDATA[ modal ]]>
                    </category>
                
                    <category>
                        <![CDATA[ html modal ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Accessibility ]]>
                    </category>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Dialog ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ jabo Landry ]]>
                </dc:creator>
                <pubDate>Fri, 17 Jul 2026 20:34:10 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/e929b316-4b85-459b-9c40-1b7928518077.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Creating pop-ups and modals on your site can be a complicated process. And it often requires a lot of boilerplate code to get started.</p>
<p>But the real challenge comes when you want to make that modal or pop-up accessible.</p>
<p>Today, I'll show you how you can use <code>&lt;dialog&gt;</code> to create an accessible modal/pop-up with minimal setup using HTML, CSS and JavaScript.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before diving in, it helps if you’re comfortable with a few basics:</p>
<ul>
<li><p><strong>CSS fundamentals</strong>: You should be familiar with common CSS terminology and concepts (selectors, properties, positioning, and so on).</p>
</li>
<li><p><strong>HTML structure</strong>: A working knowledge of how elements are organized in the DOM will make the examples easier to follow.</p>
</li>
<li><p><strong>JavaScript DOM basics</strong>: While this guide uses only minimal JavaScript, understanding how to query and manipulate DOM elements will give you more confidence as you experiment.</p>
</li>
</ul>
<p>That’s all you need. No frameworks, no heavy boilerplate. Just a foundation in the core web technologies.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-how-to-create-pop-ups-with-popover">How to Create Pop-ups with <code>popover</code></a></p>
</li>
<li><p><a href="#heading-how-to-create-a-modal-using-the-dialog-tag">How to Create a Modal Using the <code>dialog</code> Tag</a></p>
</li>
<li><p><a href="#heading-gotchas-to-pay-attention-to">Gotchas to Pay Attention to</a></p>
</li>
<li><p><a href="#heading-the-backdrop-pseudo-class">The <code>::backdrop pseudo</code> class</a></p>
</li>
<li><p><a href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ul>
<h2 id="heading-how-to-create-pop-ups-with-popover">How to Create Pop-ups with <code>popover</code></h2>
<p>Creating a pop-up from scratch using HTML, CSS and JavaScript can be quite challenging. A pop-up is a small dialog box that appears on the screen to show extra information or ask for input. Because it’s temporary by design, users expect it to disappear once they’re done interacting with it, like when they press Escape or click outside of it.</p>
<p>Using the HTML <code>popover</code> and <code>popovertarget</code> attributes, you can have several built-in accessibility and interaction behaviors, such as Escape-to-close and light dismiss, but you still need to choose the right semantic element and test keyboard and screen reader behavior.</p>
<h3 id="heading-setting-up-the-pop-up-with-html">Setting Up the Pop-up with HTML</h3>
<p>To be practical, let's create a common example and use case for pop-ups on a webpage. We'll create a nav element that displays when a user clicks a hamburger menu or the menu list.</p>
<p>First, you'll set the <code>popover</code> attribute on the pop-up container. This tells HTML to treat the containing block as a pop-up and hide it from the screen by default.</p>
<p>Then you set the <code>popovertarget</code> attribute on the element that will trigger the pop-up (like a button element or something else) to unhide the hidden element with an attribute of <code>popover</code>.</p>
<h4 id="heading-example">Example:</h4>
<pre><code class="language-html">&lt;button popovertarget="navbar-menu" id='nav-btn'&gt;open&lt;/button&gt;

&lt;nav id="navbar-menu" popover&gt;
  &lt;a href="#"&gt;Home&lt;/a&gt;
  &lt;a href="#"&gt;About&lt;/a&gt;
  &lt;a href="#"&gt;Address&lt;/a&gt;
&lt;/nav&gt;
</code></pre>
<p>With the above setup, you have a pop-up with useful built-in interaction behaviors, including Escape-to-close and light dismiss. You can hide it from the screen by pressing the <code>ESC</code> key on the keyboard or when you click anywhere else on the page (as long as it's not inside the pop-up section).</p>
<p>Remember that the <code>popover</code> attribute alone doesn't automatically make a pop-up accessible. You still need to use the appropriate semantic elements, provide accessible labels where needed, and test keyboard and screen reader behavior.</p>
<h3 id="heading-how-to-align-the-pop-up">How to Align the Pop-up</h3>
<p>Now you'll want to align the pop-up and place it where you want it to be. By default, the pop-up (or modal) that's created using either the <code>popover</code> attribute or the dialog tag will be centered on the page.</p>
<p>This is because by default elements with <code>popover</code> have a position of <code>fixed</code> and the <code>inset</code> of 0, which centers the pop-up box and a margin of <code>auto</code>.</p>
<p><strong>Note:</strong> <code>inset</code> is the shorthand for top, bottom, left and right of an element's position on the page. If you want to have the same size on all of sides of an element, use inset.</p>
<p>If you don't want your pop-up in the center, you can start by setting the element with <code>popover</code> position to absolute to isolate it from the page's flow:</p>
<pre><code class="language-css">#navbar-menu {
  position: absolute;
}
</code></pre>
<p>You can then disable the margin to 0 and positions (inset) to <code>unset</code>:</p>
<pre><code class="language-css">#navbar-menu {
  position: absolute;
  margin: 0;
  inset: unset;
}
</code></pre>
<p>After this, you can then place the popover element on the side of the page you want.</p>
<h3 id="heading-how-to-use-the-position-anchor-property">How to Use the <code>position-anchor</code> Property</h3>
<p><strong>Note:</strong> CSS Anchor Positioning is a newer feature. Check browser support before relying on it in production and provide a fallback for browsers that don't support it yet.</p>
<p>The next step is to position the element close to the element that triggers it. For it we can use the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/position-anchor"><code>position-anchor</code> property of CSS</a>.</p>
<p>The <code>position-anchor</code> property in CSS specifies a default anchor element that an absolutely or fixed-positioned element will "tether" or snap to. It lets you link a floating target (like our pop-up here) to another element on the page using only CSS.</p>
<p>In our example, we have a menu list icon or a hamburger menu that will open and close the nav element as a pop-up. We want the nav bar menu to be attached to/near the menu list that opens it.</p>
<p>So, you can add the <code>anchor-name</code> property to a menu icon. The name must be prefixed with double-dashes (and the name can be anything you want).</p>
<pre><code class="language-css">#nav-btn {
  anchor-name: --nav;
}
</code></pre>
<p>The <code>position-anchor</code> property lets you attach an element to another element identified by an anchor name. Once anchored, you can use the <code>anchor()</code> function to position the element relative to that anchor, like aligning it to the anchor’s top, bottom, or center.</p>
<pre><code class="language-css">#navbar-menu {
  position: absolute;
  margin: 0;
  inset: auto;
  position-anchor: --nav;
  top: anchor(bottom);
  right: anchor(right);
}
</code></pre>
<p>Pass the anchor name you give your anchor as value of <code>position-anchor</code> to align the nav element (<code>#navbar-menu</code> in our example) on the page around the <code>anchor-name</code> of <code>nav</code> which is <code>#nav-btn</code> in our example.</p>
<p>Then the <code>anchor()</code>positions the top of nav element on the bottom side and right side to the menu's right side.</p>
<div class="embed-wrapper"><iframe width="100%" height="350" src="https://codepen.io/jabo-arnold/embed/ogBBNNa" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>

<h2 id="heading-how-to-create-a-modal-using-the-dialog-tag">How to Create a Modal Using the <code>dialog</code> Tag</h2>
<p>Using the <code>command</code> and <code>commandfor</code> attributes on a button, you can declaratively control a <code>&lt;dialog&gt;</code>. For example, <code>command="show-modal"</code> opens it as a modal, while <code>command="close"</code> closes it.</p>
<p>Pop-up modals and pop-ups are different: with a pop-up, you can still interact with the page when the pop-up is active. But with modals or pop-up modals you can't interact with the page – modals lock the screen until they're ignored or confirmed.</p>
<p>When a <code>&lt;dialog&gt;</code> is opened as a modal, it's placed in the browser's top layer so that it appears above the rest of the page content. The rest of the document becomes inert, meaning users can't interact with it while the modal is open. The browser also creates a <code>::backdrop</code> behind the modal, which you can style to provide a visual overlay.</p>
<p>Here's an example:</p>
<pre><code class="language-html">&lt;button command="show-modal" commandfor="contact-dialog"&gt;
    open modal
&lt;/button&gt;

 &lt;dialog id="contact-dialog"&gt;
    &lt;button command="close" commandfor="contact-dialog" aria-label="close modal"&gt;
      close modal
    &lt;/button&gt;
    &lt;!--modal contents goes here--&gt;
&lt;/dialog&gt;
</code></pre>
<p>You can use the <code>command</code> attribute to close and show the modal and the <code>commandfor</code> attribute to reference the modal that's being targeted using the modal's id.</p>
<p>The <code>command</code> attribute can receive one of the following options:</p>
<ul>
<li><p><code>show-modal</code>: This option is used on the element that will trigger the modal or the dialog box to open it.</p>
</li>
<li><p><code>close</code>: This option is passed to an element and will close the modal when it's open.</p>
</li>
</ul>
<p>In the code snippet example, you can see that the button with label <code>open modal</code> has a <code>command</code> attribute with the option to <strong>show-modal</strong> and the <code>commandfor</code> attribute that's targeting the dialog element by its id.</p>
<p>The close modal button is using the <strong>close</strong> option on the <code>command</code> attribute to close the modal when clicked. It also uses <code>commandfor</code> to indicate which modal should be closed when the close button is clicked.</p>
<p>Below you'll find a demo of a modal that's created using the <code>command</code> and <code>commandfor</code> attributes:</p>
<div class="embed-wrapper"><iframe width="100%" height="350" src="https://codepen.io/jabo-arnold/embed/NPdRqWK?editors=1100" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>

<p>Try clicking the "click to pop a modal!" button on the Code Pen above to see the modal you can create by just using the <code>command</code> and <code>commandfor</code> attributes on a <code>dialog</code> tag.</p>
<h3 id="heading-create-modal-using-showmodal">Create modal using <code>showModal()</code></h3>
<p>The command API may not be supported in older browsers. Alternatively you can use JavaScript's <code>showModal()</code> method, as it's widely available in many browsers compared to <code>command</code> and <code>commandfor</code>.</p>
<pre><code class="language-javascript">const btn = document.querySelector("button");
const dialog = document.querySelector("dialog");

btn.addEventListener("click", () =&gt; {
  dialog.showModal();
});
</code></pre>
<h3 id="heading-how-to-open-a-non-modal-dialog-with-show">How to Open a Non-Modal Dialog with <code>show()</code></h3>
<p>Sometimes you may want to have an element like modal that stays interactive and doesn't block the other pages content like the <code>command</code> and <code>commandfor</code> attributes do. For this, you can use <code>show()</code> on dialog using JavaScript.</p>
<pre><code class="language-javascript">const btn = document.querySelector("button");
const dialog = document.querySelector("dialog");

btn.addEventListener("click", () =&gt; {
  dialog.show();
});
</code></pre>
<p>The above snippet keeps the rest of the page interactive while the dialog is open. This differs from opening a dialog with <code>showModal()</code> or using <code>command="show-modal"</code>, which opens the dialog as a modal and makes the rest of the document inert.</p>
<p><strong>Note</strong>: Keep in mind that <code>show()</code> isn't considered a modal but more of a dialog-like element that doesn't block interaction with the rest of the page.</p>
<h2 id="heading-gotchas-to-pay-attention-to">Gotchas to Pay Attention to</h2>
<p>When you're using element <code>popover</code>, the <code>command</code> and <code>commandfor</code> attributes, or the <code>show()</code> method, there are some "gotchas" to watch out for. Paying attention to these will help you stick to best practices.</p>
<h3 id="heading-dont-use-flex-or-grid">Don't Use Flex or Grid</h3>
<p>Directly applying layout-related styles like Flex or Grid is highly discouraged on both elements with <code>popover</code> and modal elements.</p>
<p>By default, elements with <code>popover</code> attribute, <code>command</code> and <code>commandfor</code> button attributes, and <code>show()</code> have a display of <code>none</code>. This basically means the pop-up or modal is hidden from the screen.</p>
<p>When you add <code>flex</code> or <code>grid</code> directly on element with the <code>popover</code> attribute or on a modal element, you're rewriting the modal or <code>popover</code> element's default behavior and they will be always visible on the screen. This means that you won't be able to hide the modal from the screen.</p>
<p>Check out the below example in the CodePen demo:</p>
<pre><code class="language-css">#navbar-menu {
  display: grid;
/* other styles definition*/
}
</code></pre>
<div class="embed-wrapper"><iframe width="100%" height="350" src="https://codepen.io/jabo-arnold/embed/wBgrJEv" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>

<p>You can see from the example that the nav element is always visible even if we click on the menu button to hide it again.</p>
<h4 id="heading-suggested-approach">Suggested Approach</h4>
<p>In this situation, you can either use the <code>popover-open</code> pseudo-class on an element on a property that has <code>popover</code> attributes, or you can use <code>dialog[open]</code> on the dialog element.</p>
<p>The pseudo-class styles the element based on its state when interacting with the page. So, in this case we want to give the pop-up or a modal a different layout when it's in the open state.</p>
<p>Example:</p>
<pre><code class="language-css">/* element with a popover*/
#navbar-menu:popover-open {
  display: grid;
  gap: 2rem;
}

/* using a dialog element*/
dialog[open] {
  display: grid;
  gap: 2rem;
}
</code></pre>
<h3 id="heading-background-scrolling">Background Scrolling</h3>
<p>Another thing to consider when using the <code>&lt;dialog&gt;</code> element is background scrolling. Depending on the browser and platform, the underlying page may still be scrollable while a modal dialog is open. If you want to prevent this behavior, you can explicitly disable scrolling while the dialog is open.</p>
<p>Take a look at this CodePen example:</p>
<div class="embed-wrapper"><iframe width="100%" height="350" src="https://codepen.io/jabo-arnold/embed/NPdRqWK?editors=1100" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>

<p>When you click on the button to show the modal, you can still see the scrollbar on the page, and you can scroll around the page.</p>
<h4 id="heading-suggested-approach">Suggested Approach</h4>
<p>To deal with this issue we can use the <code>has</code> pseudo class. <code>has</code> helps select the parent element based on its children's state. On the root element or HTML element, you can check if it has an open modal. If so, you can set the root element or HTML element overflow to hidden.</p>
<p>For example:</p>
<pre><code class="language-css">html:has(dialog[open]) {
  overflow: hidden;
}
</code></pre>
<p>This will hide the scrollbar on the page when the modal is open. Keep in mind that you can also use any parent element to wrap the <code>dialog</code> tag with the <code>has</code> pseudo class. It doesn't always have to be the root element or HTML element.</p>
<h2 id="heading-the-backdrop-pseudo-class">The <code>::backdrop</code> Pseudo Class</h2>
<p>If you want to use a customized overlay color on the <code>popover</code> element or modal element, you can use the <code>::backdrop</code> pseudo class to customize the appearance for the modal overlay color.</p>
<p><strong>Example:</strong></p>
<pre><code class="language-css">dialog::backdrop {
  background: rgba(43, 50, 200, 0.4);
}
</code></pre>
<p>This will apply the overlay with defined <code>RGB</code> colors and the <code>opacity</code> of 0.4 on the overlay to have a little transparent on the overlay.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>I hope you've gained something new from this article, and that it will help you start using these techniques in your projects.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Responsive Designs and Scroll Effects with CSS Container Queries ]]>
                </title>
                <description>
                    <![CDATA[ Container queries let you target specific sections of your webpage and apply styles to create customizable, responsive designs based on the container's size rather than that of the viewport. This guid ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-responsive-designs-and-scroll-effects-with-css-container-queries/</link>
                <guid isPermaLink="false">6a1f667b3090fee41f05a0d3</guid>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Container queries ]]>
                    </category>
                
                    <category>
                        <![CDATA[ scroll ]]>
                    </category>
                
                    <category>
                        <![CDATA[ media queries ]]>
                    </category>
                
                    <category>
                        <![CDATA[ scroll animation ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ jabo Landry ]]>
                </dc:creator>
                <pubDate>Tue, 02 Jun 2026 23:25:47 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/dc70f649-2837-4768-be69-e4c1e85dcb03.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Container queries let you target specific sections of your webpage and apply styles to create customizable, responsive designs based on the container's size rather than that of the viewport.</p>
<p>This guide will teach you this useful alternative for responsive designs. You'll also learn about on-scroll effects in CSS.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before diving into scrollable queries and container-based scroll effects, make sure you have:</p>
<ul>
<li><p><strong>Basic familiarity with CSS features and key terms</strong> — understanding properties like <code>height</code>, <code>overflow</code>, and how they affect layout.</p>
</li>
<li><p><strong>Experience writing vanilla CSS</strong> — being comfortable with selectors, rulesets, and applying styles without relying on frameworks.</p>
</li>
<li><p><strong>A working knowledge of HTML structure</strong> — since scroll behavior depends on parent elements, knowing how containers and child elements interact is essential.</p>
</li>
<li><p><em>(Optional but helpful)</em> Some exposure to responsive design concepts, so you can see how scrollable parents fit into broader layout strategies.</p>
</li>
</ul>
<h2 id="heading-what-well-cover">What We'll Cover:</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-how-to-use-container-queries">How to Use Container Queries</a></p>
</li>
<li><p><a href="#heading-difference-between-container-queries-and-media-queries">Difference Between Container Queries and Media queries</a></p>
</li>
<li><p><a href="#heading-container-scroll-state">Container Scroll-state</a></p>
</li>
<li><p><a href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
</ul>
<h2 id="heading-how-to-use-container-queries">How to Use Container Queries</h2>
<p>Usually, when making responsive designs, you use a media query that adds style to the page based on the page's width viewport, but container queries shift the narrative by adding styles based on the specific elements container or parent element size.</p>
<p>Take a navbar as an example. Let's say you want to have its links wrapped on top of each other when the size of a <code>nav</code> element size is less than <code>450px</code> and also change the background color when the screen size is less than <code>450px</code>.</p>
<p>To make <code>nav</code> element links wrap on top of each other when the <code>nav</code> element size is less than <code>450px</code>, we start by wrapping the links inside a single parent <code>nav</code>:</p>
<pre><code class="language-html">   &lt;nav&gt;
      &lt;img src="" alt="logo" /&gt;
      &lt;ul&gt;
        &lt;a href=""&gt;home&lt;/a&gt;
        &lt;a href=""&gt;about&lt;/a&gt;
        &lt;a href=""&gt;services&lt;/a&gt;
        &lt;a href=""&gt;contact&lt;/a&gt;
      &lt;/ul&gt;
    &lt;/nav&gt;
</code></pre>
<p>Then use <code>container-type</code> to define a container query on the wrapping element or parent (<code>nav</code>) element. It can have one of the following values:</p>
<ul>
<li><p><code>inline-size</code>: Used when you want to track the x-axis of the parent element on the page (width)</p>
</li>
<li><p><code>block-size</code>: Used when you want to track the y-axis of the parent element on the page (height)</p>
</li>
<li><p>Container Scroll-state: Used when you want to track the parent element when scrolled and when elements with position <code>sticky</code> are stuck on the screen.</p>
</li>
</ul>
<p>For most cases, you will need to track the size of an element against the x-axis size, and you must use the <code>container-type</code> on a parent element because you want to apply styles based on the parent element size. Here, we'll use the <code>nav</code> because it is the parent element.</p>
<pre><code class="language-css">nav { 
  container-type: inline-size;
}
</code></pre>
<p>You then define a container query with an at symbol (<code>@</code>) followed by the <code>container</code> keyword.</p>
<p>Then check if the <code>width</code> of the parent container (<code>nav</code>) gets narrower than <code>450px</code>. If the condition is true, add a <code>flex-wrap</code> to links element:</p>
<pre><code class="language-css">@container (width &gt; 450px) {
  ul {
    flex-wrap: wrap;
  }
}
</code></pre>
<h2 id="heading-difference-between-container-queries-and-media-queries">Difference Between Container Queries and Media queries</h2>
<p>Take a look at the code below for media queries:</p>
<pre><code class="language-css">
@media (width &lt; 450px) {
  nav {
    background: green;
  }
}
</code></pre>
<p>In the media query above, the <code>nav</code> element has a <code>green</code> background-color when the screen size is less than <code>450px</code>.</p>
<p>For container queries, the elements are getting stacked on top of each other when the <code>nav</code> element size is less than <code>450px</code>.</p>
<p>So, the difference is that media-queries considers <strong>the whole screen size</strong> while container-queries consider the <strong>parent element size</strong>.</p>
<p><strong>Example:</strong></p>
<div class="embed-wrapper"><iframe width="100%" height="350" src="https://codepen.io/jabo-arnold/embed/dPprjqM" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>

<h2 id="heading-container-scroll-state">Container Scroll-state</h2>
<p>You can also use container queries to style an element on scroll or to style an element with a position sticky when it's stuck on the page.</p>
<p><strong>Note</strong>: Container <strong>scroll-state</strong> browser support isn't very good. It's supported in about 71% of all browsers currently, with the notable exceptions of Safari and Firefox. Be cautious when using it in production.</p>
<h3 id="heading-scrollable-queries">Scrollable Queries</h3>
<p>A scrollable query is used to check for parent element scrollbar behavior. If there is a scroll behavior on the parent element, then the container query for scrolling will be applied.</p>
<p>To use the scrollable queries, you need to have a parent that has a scroll behavior. HTML is the best fit for such an example because by default HTML has a scroll behavior on it.</p>
<p>To make an element scrollable, set a fixed height and apply <code>overflow</code>. This ensures a scroll bar appears whenever content exceeds that defined space.</p>
<h4 id="heading-example">Example:</h4>
<p>We’ll design a simple page that displays a sidebar table of contents when the page is scrolled down and hides it when the user returns to the top.</p>
<p>First, make HTML a <code>container-type</code> of <code>scroll-state</code>:</p>
<pre><code class="language-css">html {
  container-type: scroll-state;
}
</code></pre>
<p>Style the table of contents section and position it at the top right of the page.</p>
<pre><code class="language-css">.toc {
  position: fixed;
  top: 5rem;
  left:90%;
  align-self: start;
  opacity: 0;
}
</code></pre>
<p>You positioned the table of contents to the right of the page and fixed it to the top of the screen by <code>5rem</code>. You also hid it by default by adding an <code>opacity</code> of <code>0</code>.</p>
<p>Now you need to check if the container scrollbar can be scrolled to the top of its container. If true, unhide the table of contents:</p>
<pre><code class="language-css">@container scroll-state(scrollable: top) {
  .toc {
    transition: opacity 0.4s linear;
    opacity: 1;  
  }
}
</code></pre>
<p>You defined a container query, then followed it with the scroll-state keyword. This checks whether the scrollbar on a parent element can be scrolled.</p>
<p>Use <code>scrollable</code> to define the scroll direction. Setting it to <code>top</code> tests whether the container can scroll upward, while setting it to <code>bottom</code> tests whether it can scroll downward.</p>
<div class="embed-wrapper"><iframe width="100%" height="350" src="https://codepen.io/jabo-arnold/embed/GgjewRp" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>

<p>You can see that as long there's a space for scrolling to the top, the table-of- contents will be visible, but when you can't scroll to the top, the table of contents will be hidden.</p>
<p>You can also check whether you can scroll in both top and bottom directions using the <code>y</code> specifier to specify the container's top and bottom scroll directions.</p>
<h4 id="heading-example">Example:</h4>
<div class="embed-wrapper"><iframe width="100%" height="350" src="https://codepen.io/jabo-arnold/embed/ogYgvJb?editors=1100" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>

<p>You can see from the example that the table of contents will always be visible on the screen because the scroll bar can scroll both ways (top and bottom)</p>
<h3 id="heading-stuck-queries">Stuck Queries</h3>
<p>Stuck queries are used to inspect the element that have a <code>position</code> of <code>sticky</code> and applies styles when the element is stuck on the page.</p>
<p>These are great if you want to style elements that have a sticky position and you want them to have a certain style(s) when they get stuck on the page.</p>
<h4 id="heading-example">Example:</h4>
<p>We'll design a simple navbar that will change its <code>box-shadow</code> when its stuck at the top of the screen.</p>
<p>Here, you'll set a wrapper element that wraps <code>nav</code> and makes it a container query, and also gives it a <code>position</code> of <code>sticky</code> to be able to track if it is stuck at the top of the screen.</p>
<pre><code class="language-css">header {
  position: sticky;
  top: 0rem;
  container-type: scroll-state;
}
</code></pre>
<p>You can then define <code>@container</code> to execute when the element gets stuck on the screen:</p>
<pre><code class="language-css">@container scroll-state(stuck:top) {
  nav {
    box-shadow:
      0 14px 28px rgba(0, 0, 0, 0.55),
      0 1px 0 rgba(255, 255, 255, 0.06) inset;
    background-color: #0a0a0a;
  }
}
</code></pre>
<p>You checked for <code>scroll-state</code> behavior and then, inside the parenthesis, checked if the element with <code>position: sticky</code> is <strong>stuck</strong> at top of the page. If true, the background color of the <code>nav</code> changes and a <code>box-shadow</code> is added.</p>
<p>Final results should look like this:</p>
<div class="embed-wrapper"><iframe width="100%" height="350" src="https://codepen.io/jabo-arnold/embed/gbwEQZR" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="CodePen embed" scrolling="no" allowtransparency="true" allowfullscreen="true" loading="lazy"></iframe></div>

<p>By default, the <code>nav</code> doesn't have a <code>box-shadow</code>, but when you scroll and the <code>nav</code> element gets stuck at the top of the page, <code>box-shadow</code> and other rules defined within the stuck <code>@container</code> query definition will be applied to the <code>nav</code> element.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>In this article, you learned how to set up and work with container queries to make more customizable and responsive designs, and some cool scroll effects.</p>
<p>The examples provided in this guide are not the only things you can do with container queries. The examples were designed to help you think of other alternatives and contexts where you can apply container queries to match your exact needs and design.</p>
<p>Container queries have high browser support, which is currently at 95% and supported in all major browsers.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
