Build a Random Advice Generator

Fetch live data from an API and build an interactive web app with vanilla JS.

Introduction

30 Second Summary

Every website you use pulls data from somewhere on the internet. Whether it's a weather app, a social feed, or a news site, they all fetch information and display it dynamically.

In this project, you will build a Random Advice Generator that fetches live data from an API and displays it in a styled, interactive web page using vanilla JavaScript.

What You'll Build

A web app that calls the Advice Slip API to fetch random pieces of advice, then displays them in a polished card-based UI with loading states and error handling.

By the end of this project, you'll have:

  • A complete HTML/CSS/JavaScript project with a gradient background, card layout, and hover animations.
  • Hands-on experience with JavaScript's Fetch API and async/await for pulling live data from the internet.
  • A fully interactive page with loading indicators and user-friendly error messages that handle real-world network issues.
  • Secret Mission: Add a search feature that lets users find advice by keyword.

Are there any prerequisites?

None. This guide includes full setup instructions for every tool you need. You don't need any prior coding experience to get started.

Before We Start

Before you can build anything, you need a workspace. Every web project starts with three ingredients: a code editor for writing your files, a project folder to keep everything organised, and the basic files themselves.

In this step, you will install Cursor (your code editor), create a project folder on your Desktop, and set up the three files that make up every front-end web app.

In this step, get ready to:
  • Install Cursor, a code editor for writing HTML, CSS, and JavaScript.
  • Create a project folder with three files for your web app.
  • Open your HTML file in a browser to confirm everything is connected.
Install Cursor

Cursor is a code editor that lets you write and manage your project files. It is where you will write all the HTML, CSS, and JavaScript for this project.

✔️ I already have Cursor

Great, you are ready to go. Skip ahead to the next substep.

ⓧ I don't have Cursor yet

  • Open your web browser and go to the Cursor download page:
  • https://cursor.com/download
  • Click the download button for your operating system (macOS or Windows).
  • Run the downloaded installer.
  • On macOS, drag Cursor.app into your Applications folder.
  • On Windows, follow the setup wizard and accept the defaults.

Once the installer finishes, Cursor is ready to open.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Cursor and press Enter to open it.

What is Cursor?

Cursor is a code editor built on the same foundation as VS Code. It gives you syntax highlighting, file management, and a built-in terminal. You will use it to create and edit all the files in this project.

Cursor won't open?

On macOS, you may see a security warning because Cursor was downloaded from the internet. Right-click the app and select Open to bypass the warning.

On Windows, make sure you ran the installer as an administrator if it failed the first time.

Help me open Cursor on my computer.

Create your project folder and files

Your project needs a dedicated folder to live in. Inside that folder, you will create the three files that make up every front-end web app: one for structure, one for styling, and one for interactivity.

  • In Cursor, open the built-in terminal by pressing Ctrl+` (backtick key, top-left of your keyboard). This works on both macOS and Windows.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir advice-generator
cd advice-generator
  • Create the three project files by running this command:
touch index.html style.css script.js
  • Confirm the files exist by running:
ls

You should see index.html, style.css, and script.js listed in the output.

Don't see three files?

Make sure you ran the touch command from inside the advice-generator folder. If ls shows nothing, run pwd to check your current directory. It should end with advice-generator.

Help me find my project files in the terminal.

  • Open the advice-generator folder in Cursor by clicking File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the advice-generator folder, and click Open.

You should see index.html, style.css, and script.js listed in the Explorer sidebar on the left.

Open your HTML file in the browser

The last check is to confirm your browser can load the HTML file. Even though the file is empty, opening it now proves the connection between your project folder and the browser works.

  • Open Finder (macOS) or File Explorer (Windows) and navigate to your Desktop.
  • Open the advice-generator folder.
  • Double-click index.html to open it in your default browser.

You should see a blank white page. That is expected because the file is empty. The important thing is that it loaded without any error.

Browser showing a file listing instead of a blank page?

This happens if you opened the folder rather than the file. Make sure you double-clicked index.html specifically, not the advice-generator folder itself.

If your browser is not the default app for .html files, right-click the file, select Open with, and choose Chrome or Edge.

Help me open index.html in my browser.

Your workspace is ready to go. Next up, you will build the HTML structure that gives your advice generator its shape.

Set Up Your Project

Every web app starts with three files working together: HTML for structure, CSS for styling, and JavaScript for interactivity. Your project folder already has all three files ready.

Right now those files are empty. In this step, you'll write the HTML boilerplate that connects them together. This is the foundation every web project needs before you add any content.

In this step, get ready to:
  • Write the HTML5 boilerplate for your page.
  • Link your CSS and JavaScript files to the HTML.
  • Confirm everything loads correctly in the browser.
Write the HTML5 boilerplate

HTML boilerplate is the starter code that tells the browser what kind of document this is, how to display it, and which other files to load. Without it, your CSS and JavaScript have no way to connect to the page.

  • In Cursor, click on index.html in the Explorer sidebar to open it.
  • Paste the following code into the empty file:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Random Advice Generator</title>
    <!-- Link to our CSS file for styling -->
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <!-- Link to our JavaScript file for interactivity -->
    <script src="script.js"></script>
</body>
</html>

What does this code do?

This boilerplate sets up the essential structure of your web page.

  • <!DOCTYPE html> tells the browser this is a modern HTML5 document.
  • The <meta charset="UTF-8"> and viewport tags handle character encoding and mobile responsiveness.
  • <link rel="stylesheet" href="style.css"> connects your CSS file so the browser can apply styles to the page.
  • <script src="script.js"></script> connects your JavaScript file. It's placed at the bottom of the body so the HTML loads first.
  • Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Switch back to your browser and refresh the page. You should see the tab title change to Random Advice Generator and the page itself will still be blank (that's expected).

Tab title still showing the file path?

  • Make sure you saved the file. Look for the dot on the file tab in Cursor that indicates unsaved changes.
  • Check that the file is named exactly index.html (not index.html.txt or similar).
  • Try a hard refresh with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows) to clear the browser cache.

Why isn't my page title updating after I save index.html?

✔️ Awesome, I've got everything!

Great. Double check you've saved your file and you're ready for the next step.

ⓧ I'd like to double check the full code

Here's what your index.html should look like:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Random Advice Generator</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <script src="script.js"></script>
</body>
</html>

Your project scaffolding is ready. All three files are connected and your browser can load the page. Next up, you'll build the HTML elements that make up your advice generator's interface.

Build the HTML Structure

Your HTML file loads in the browser, but it shows a blank page. That's because the <body> is empty apart from the script tag.

In this step, you'll add the visible page structure. Every element you place here will become interactive later when JavaScript brings it to life.

In this step, get ready to:
  • Confirm your HTML skeleton links CSS and JS correctly.
  • Add a card container with a heading, advice display, and button.
  • Add loading and error message elements for later use.
Confirm your HTML skeleton

Your index.html file from the previous step already has the essential wiring in place.

  • Switch back to index.html in Cursor.
  • Confirm you see the <link rel="stylesheet" href="style.css"> line in the <head> section.
  • Confirm you see <script src="script.js"></script> just before the closing </body> tag.

Why is the script tag at the bottom?

Placing <script> at the bottom of the body ensures all HTML elements exist before JavaScript tries to interact with them. If the script ran first, it would try to find elements that haven't been created yet.

Add the page content structure

Every piece of content on a web page lives inside HTML elements. You'll create a card layout with dedicated elements for the advice text, an ID label, a button, and hidden elements that will appear during loading or errors.

  • In index.html, find the opening <body> tag.
  • Add the following code between <body> and <script src="script.js"></script>:
    <!-- Wrapper div to center the content on the page -->
    <div class="container">
        <h1>Random Advice Generator</h1>
        <!-- Card holds all interactive elements -->
        <div class="card">
            <!-- Displays the advice slip number -->
            <p id="advice-id" class="advice-id">Advice #</p>
            <!-- Shows the advice text from the API -->
            <p id="advice-text" class="advice-text">Click the button to get some advice!</p>
            <!-- Error message, hidden until something goes wrong -->
            <p id="error-message" class="error-message"></p>
            <!-- Loading text, hidden until a fetch is in progress -->
            <p id="loading" class="loading">Loading...</p>
            <!-- Button the user clicks to get new advice -->
            <button id="get-advice-btn" class="btn">Get Advice</button>
        </div>
    </div>

What does this code do?

This HTML creates a card-style layout with several elements that each have a specific job.

  • The .container div wraps everything so you can center the content on the page with CSS later.
  • The .card div groups the interactive elements into a visual card that you'll style with rounded corners and a shadow.
  • The #advice-id and #advice-text paragraphs are where JavaScript will inject the advice data from the API.
  • The #error-message and #loading elements start invisible. CSS will hide them by default, and JavaScript will show them at the right moment.
  • The #get-advice-btn button is what the user clicks to trigger the advice fetch.

Why both an id and a class?

Each element has both an id and a class. The id is for JavaScript to find the element quickly. The class is for CSS to style it.

  • Save index.html.
  • Refresh your browser. You should see the heading "Random Advice Generator", the text "Click the button to get some advice!", the text "Advice #", the word "Loading...", and a plain unstyled "Get Advice" button.

Don't see any content in the browser?

  • Make sure you saved index.html after pasting the new code. Unsaved changes won't appear in the browser.
  • Check that the new code sits between the <body> tag and the <script> tag. Code outside the body won't render on the page.
  • Confirm the file open in your browser tab is the same index.html file from your project folder. The URL in the address bar should show the path to your project.

Still stuck? Help me figure out why my HTML page is blank.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here's what your complete index.html should look like at this point:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Random Advice Generator</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>Random Advice Generator</h1>
        <div class="card">
            <p id="advice-id" class="advice-id">Advice #</p>
            <p id="advice-text" class="advice-text">Click the button to get some advice!</p>
            <p id="error-message" class="error-message"></p>
            <p id="loading" class="loading">Loading...</p>
            <button id="get-advice-btn" class="btn">Get Advice</button>
        </div>
    </div>
    <script src="script.js"></script>
</body>
</html>

Your page structure is complete. Right now it looks plain and unstyled, but every element is in place for JavaScript to target later. Next up, you'll add CSS to transform this raw HTML into a polished card layout with a gradient background.

Style the Page

Your HTML structure is in place. Right now, though, it looks like plain unstyled text on a white page.

In this step, you'll transform that bare structure into a polished card interface with a dark gradient background, clean typography, and a button that responds to interaction. All of this lives in style.css.

In this step, get ready to:
  • Add a gradient background and center the card using flexbox.
  • Style the card with rounded corners, a box shadow, and clean typography.
  • Style the button with a hover effect, and add visibility rules for the loading and error states.
Add a gradient background and center the card

Every browser applies its own default margins and padding to elements. A CSS reset removes those defaults so your design looks consistent everywhere.

  • In Cursor, click on style.css in the Explorer sidebar to open it.
  • Add the CSS reset and body styles by pasting the following code:
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    min-height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    background: linear-gradient(135deg, #1a1a2e, #16213e, #0f3460);
}

What does this code do?

The * selector targets every element on the page. It resets margins and padding to zero so you have full control over spacing.

The body uses flexbox to center its content both horizontally and vertically. The linear-gradient creates a dark blue background that transitions between three shades at a 135-degree angle.

  • Save style.css and refresh your browser.
  • You should see a dark gradient background filling the entire viewport, with your unstyled white text and button pushed to the center of the page.

Still seeing a white background?

Make sure your index.html has <link rel="stylesheet" href="style.css"> in the <head> section. Without this link, the browser doesn't know about your stylesheet.

Also check that style.css is saved in the same folder as index.html.

Help me debug why my CSS isn't loading.

Now add styles for the container and heading. These position your content and make the title visible against the dark background.

  • Add the following code below the body block in style.css:
.container {
    text-align: center;
    padding: 20px;
}

h1 {
    color: #ffffff;
    margin-bottom: 30px;
    font-size: 2rem;
}

What does this code do?

The .container centers all text inside it and adds breathing room around the edges.

The h1 gets white text so it's readable against the dark gradient, plus a bottom margin to separate it from the card below.

  • Save style.css and refresh your browser.
  • You should see "Random Advice Generator" in large white text above your content, with everything centered on the page.
Style the card with rounded corners and clean typography

The card is the main visual container that holds your advice. It needs to stand out from the dark background and make the text easy to read.

  • Add the card styles below the h1 block in style.css:
.card {
    background: #ffffff;
    border-radius: 16px;
    padding: 40px 30px;
    max-width: 500px;
    width: 90vw;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}

What does this code do?

The white background and border-radius: 16px create a smooth rounded card shape.

The box-shadow adds a soft drop shadow that makes the card appear to float above the gradient. The max-width keeps it readable on large screens, while width: 90vw makes it responsive on smaller ones.

  • Save style.css and refresh your browser.
  • You should see a white rounded card floating in the center of the dark background with a subtle shadow beneath it.

Card looks too wide or stretches edge to edge?

Check that you have both max-width: 500px and width: 90vw in your .card rule. The max-width caps the size while width: 90vw allows it to shrink on narrow screens.

Help me fix my card sizing.

Next, style the text elements inside the card so each piece of information has its own visual hierarchy.

  • Add the advice text styles below the .card block:
.advice-id {
    color: #52ffa8;
    font-size: 0.85rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 3px;
    margin-bottom: 16px;
}

.advice-text {
    color: #333333;
    font-size: 1.4rem;
    line-height: 1.6;
    margin-bottom: 30px;
    font-weight: 300;
}

What does this code do?

The .advice-id gets a bright green color, uppercase letters, and wide letter-spacing. This makes it look like a subtle label above the main text.

The .advice-text is larger and lighter weight (300) for a clean, readable feel. The generous line-height ensures multi-line advice doesn't feel cramped.

  • Save style.css and refresh your browser.
  • You should see "Advice #" in small green uppercase text, with "Click the button to get some advice!" displayed larger below it in dark gray.
Style the button and add visibility rules for loading and error states

The loading and error messages should stay hidden until JavaScript activates them. You'll use a .visible class pattern where elements are hidden by default and only appear when the class is added.

  • Add the error and loading state styles below the .advice-text block:
.error-message {
    color: #e74c3c;
    font-size: 0.9rem;
    margin-bottom: 16px;
    display: none;
}

.error-message.visible {
    display: block;
}

.loading {
    color: #888888;
    font-size: 0.9rem;
    margin-bottom: 16px;
    display: none;
}

.loading.visible {
    display: block;
}

What does this code do?

Both .error-message and .loading are hidden with display: none by default. When JavaScript adds the visible class, they switch to display: block and appear on the page.

This pattern keeps your HTML clean. The elements exist in the DOM at all times, but CSS controls whether the user sees them.

  • Save style.css and refresh your browser.
  • The "Loading..." text that was previously visible should now be hidden. Your card should show only the advice ID, the placeholder text, and the button.

Still seeing "Loading..." text in the card?

Make sure you wrote .loading with the dot prefix (class selector). Without the dot, CSS looks for an HTML element called <loading> which doesn't exist.

Help me figure out why my loading text is still showing.

Finally, style the button with a gradient, a hover animation, and a disabled state for when it's clicked.

  • Add the button styles below the .loading.visible block:
.btn {
    background: linear-gradient(135deg, #52ffa8, #00c9ff);
    color: #1a1a2e;
    border: none;
    padding: 14px 32px;
    font-size: 1rem;
    font-weight: 700;
    border-radius: 30px;
    cursor: pointer;
    transition: transform 0.2s, box-shadow 0.2s;
}

.btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 20px rgba(82, 255, 168, 0.4);
}

.btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
}

What does this code do?

The .btn gets the same green-to-blue gradient as the accent color scheme. The transition property makes the hover effect animate smoothly over 0.2 seconds instead of snapping instantly.

On hover, the button lifts up by 2 pixels and gains a green glow. When disabled, it fades out and shows a "not allowed" cursor to signal that clicking won't do anything.

  • Save style.css and refresh your browser.
  • Hover over the "Get Advice" button. You should see it lift up slightly with a green glow appearing beneath it.

Button doesn't move on hover?

Check that your .btn rule includes transition: transform 0.2s, box-shadow 0.2s; and that .btn:hover has transform: translateY(-2px);. The transition makes the movement smooth, while the transform does the actual lifting.

Help me debug my button hover effect.

✔️ Awesome, I've got everything!

Great. Double check you've saved style.css before moving on.

ⓧ I'd like to double check the full code

Here is the full style.css file after this step:

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    min-height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    background: linear-gradient(135deg, #1a1a2e, #16213e, #0f3460);
}

.container {
    text-align: center;
    padding: 20px;
}

h1 {
    color: #ffffff;
    margin-bottom: 30px;
    font-size: 2rem;
}

.card {
    background: #ffffff;
    border-radius: 16px;
    padding: 40px 30px;
    max-width: 500px;
    width: 90vw;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}

.advice-id {
    color: #52ffa8;
    font-size: 0.85rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 3px;
    margin-bottom: 16px;
}

.advice-text {
    color: #333333;
    font-size: 1.4rem;
    line-height: 1.6;
    margin-bottom: 30px;
    font-weight: 300;
}

.error-message {
    color: #e74c3c;
    font-size: 0.9rem;
    margin-bottom: 16px;
    display: none;
}

.error-message.visible {
    display: block;
}

.loading {
    color: #888888;
    font-size: 0.9rem;
    margin-bottom: 16px;
    display: none;
}

.loading.visible {
    display: block;
}

.btn {
    background: linear-gradient(135deg, #52ffa8, #00c9ff);
    color: #1a1a2e;
    border: none;
    padding: 14px 32px;
    font-size: 1rem;
    font-weight: 700;
    border-radius: 30px;
    cursor: pointer;
    transition: transform 0.2s, box-shadow 0.2s;
}

.btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 20px rgba(82, 255, 168, 0.4);
}

.btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
}

Your page looks polished and professional. Next up, you'll bring it to life by writing the JavaScript that fetches real advice from the internet and displays it on the card.

Fetch and Display Random Advice

Your page looks great, but the button doesn't do anything yet. The whole point of this app is pulling live data from the internet and displaying it on the page.

In this step, you'll write JavaScript that reaches out to a real API, grabs a random piece of advice, and updates the page with it. This is the core skill behind every interactive web app.

In this step, get ready to:
  • Write an async function that fetches advice from a live API.
  • Parse the response and update the page with the advice text and ID.
  • Wire up the button so clicking it triggers the fetch.
Grab references to your HTML elements

Before your JavaScript can change anything on the page, it needs a way to find each HTML element. You do this with DOM manipulation, which lets your code grab elements by their id and change their content.

  • In Cursor, open script.js and add the following code:
// Grab references to the HTML elements we'll update
const adviceTextElement = document.getElementById('advice-text');
const adviceIdElement = document.getElementById('advice-id');
const errorMessageElement = document.getElementById('error-message');
const loadingElement = document.getElementById('loading');
const getAdviceBtn = document.getElementById('get-advice-btn');

What does this code do?

Each line uses document.getElementById() to find an HTML element by its id attribute and store it in a variable. Now you can change that element's text, visibility, or style from JavaScript.

For example, adviceTextElement points to the <p id="advice-text"> element in your HTML. Later, you'll update its textContent to show the advice.

You won't see a change in the browser yet. The next chunk uses these references to display live data.

Fetch advice from the API and wire up the button

The Fetch API lets your browser request data from a server. Combined with async/await, you can write code that waits for the server's response before continuing.

  • Add the following code below your DOM references in script.js:
// Fetch a random advice slip from the API
async function fetchAdvice() {
    try {
        const response = await fetch('https://api.adviceslip.com/advice');

        // Throw an error if the response status is not OK
        if (!response.ok) {
            throw new Error(`Response status: ${response.status}`);
        }

        // Parse the JSON body of the response
        const data = await response.json();

        // Update the page with the advice text and ID
        adviceIdElement.textContent = `Advice #${data.slip.slip_id}`;
        adviceTextElement.textContent = `"${data.slip.advice}"`;
    } catch (error) {
        // Show an error message if anything goes wrong
        errorMessageElement.textContent = 'Something went wrong. Please try again!';
        errorMessageElement.classList.add('visible');
    }
}

// Run fetchAdvice every time the button is clicked
getAdviceBtn.addEventListener('click', fetchAdvice);

What does this code do?

  • async function fetchAdvice() declares a function that can use await to pause until the API responds.
  • fetch('https://api.adviceslip.com/advice') sends a request to the Advice Slip API, which returns a random piece of advice.
  • response.json() converts the raw response into a JavaScript object you can read.
  • The try/catch block handles network failures gracefully by showing an error message instead of crashing.
  • addEventListener('click', fetchAdvice) tells the browser to run your function every time the button is clicked.

What is the Advice Slip API?

The Advice Slip API is a free, public API that requires no API key or signup. It returns JSON data shaped like: { "slip": { "slip_id": "2", "advice": "Smile and the world smiles with you." } }.

Your code reads data.slip.slip_id for the number and data.slip.advice for the advice text.

  • Save script.js.
  • Switch to your browser and refresh the page with your index.html file open.
  • Click the Get Advice button. You should see the card update with a numbered advice slip and a quote in the advice text area.

Getting the same advice twice?

The Advice Slip API caches responses for 2 seconds. If you click the button rapidly, you may get the same advice. Wait a couple of seconds between clicks for a fresh result.

Not seeing any advice appear?

  • Make sure you saved script.js before refreshing the browser.
  • Open your browser's DevTools console (press Cmd+Option+J on macOS or Ctrl+Shift+J on Windows) and look for red error messages. A typo in an element ID will cause a "Cannot read properties of null" error.
  • Check that the <script src="script.js"></script> tag is at the bottom of the <body> in your HTML, not inside <head>.

Help me debug why my Get Advice button isn't working.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here's the complete script.js file so far:

const adviceTextElement = document.getElementById('advice-text');
const adviceIdElement = document.getElementById('advice-id');
const errorMessageElement = document.getElementById('error-message');
const loadingElement = document.getElementById('loading');
const getAdviceBtn = document.getElementById('get-advice-btn');

async function fetchAdvice() {
    try {
        const response = await fetch('https://api.adviceslip.com/advice');

        if (!response.ok) {
            throw new Error(`Response status: ${response.status}`);
        }

        const data = await response.json();

        adviceIdElement.textContent = `Advice #${data.slip.slip_id}`;
        adviceTextElement.textContent = `"${data.slip.advice}"`;
    } catch (error) {
        errorMessageElement.textContent = 'Something went wrong. Please try again!';
        errorMessageElement.classList.add('visible');
    }
}

getAdviceBtn.addEventListener('click', fetchAdvice);

Your app now fetches and displays live advice from the internet. Next up, you'll add loading and error states so the user always knows what's happening.

Add Loading and Error States

Your app fetches and displays advice, but right now there's no visual feedback while the request is happening. The user clicks the button and waits in silence, unsure if anything is working.

In this step, you'll add a loading indicator that appears while the fetch is in progress, disable the button to prevent double-clicks, and gracefully handle any errors that might occur.

In this step, get ready to:
  • Show a loading indicator and disable the button while fetching.
  • Use a finally block to always clean up the loading state.
  • Clear old error messages on each new fetch attempt.
Show the loading state when a fetch starts

Every time the user clicks the button, three things need to happen before the fetch begins. The loading text should appear, any previous error message should disappear, and the button should be disabled so the user can't spam-click it.

  • In script.js, find the opening line of your fetchAdvice() function. It currently looks like this:
async function fetchAdvice() {
    try {
  • Add the following three lines between async function fetchAdvice() { and try {:
async function fetchAdvice() {
    // Show loading and reset error state before fetching
    loadingElement.classList.add('visible');
    errorMessageElement.classList.remove('visible');
    getAdviceBtn.disabled = true;

    try {

What does this code do?

  • loadingElement.classList.add('visible') adds the visible class to the loading paragraph, switching it from display: none to display: block.
  • errorMessageElement.classList.remove('visible') hides any error message left over from a previous failed attempt.
  • getAdviceBtn.disabled = true grays out the button so the user can't click it again mid-fetch.
  • Save script.js.

You won't see a change yet. The loading text appears, but it never disappears because we haven't told it to hide again. The next chunk fixes that.

Add a finally block to always clean up

A finally block runs after try and catch regardless of whether the fetch succeeded or failed. This makes it the perfect place to hide the loading indicator and re-enable the button.

  • In script.js, find the closing } of your catch block. It currently looks like this:
    } catch (error) {
        errorMessageElement.textContent = 'Something went wrong. Please try again!';
        errorMessageElement.classList.add('visible');
    }
  • Add a finally block immediately after the catch block's closing } so the end of your function looks like this:
    } catch (error) {
        errorMessageElement.textContent = 'Something went wrong. Please try again!';
        errorMessageElement.classList.add('visible');
    } finally {
        // Always hide loading and re-enable the button
        loadingElement.classList.remove('visible');
        getAdviceBtn.disabled = false;
    }

Why use finally instead of putting this in try?

If you put the cleanup lines at the end of try, they would never run when an error occurs. The code jumps straight to catch on an error, skipping anything below the failing line.

The finally block guarantees the cleanup runs no matter what. The loading indicator always disappears and the button always becomes clickable again.

  • Save script.js and refresh your browser.
  • Click the Get Advice button. You should briefly see "Loading..." appear, the button gray out, and then new advice replace the loading text.

The loading flash is fast

The Advice Slip API responds quickly, so the loading text may only flash for a split second. That's normal. The important thing is that the button briefly disables and the loading indicator appears.

Loading text never disappears?

  • Make sure your finally block is placed after the catch block's closing }, not inside it.
  • Check for typos in classList.remove('visible'). The class name must match exactly what your CSS expects.
  • Open the browser console (Cmd+Option+J on macOS or Ctrl+Shift+J on Windows) and look for any red error messages.

Why is my loading indicator stuck and the button still disabled?

Test the error handling

Your catch block already displays an error message when the fetch fails. The new errorMessageElement.classList.remove('visible') line at the top of the function ensures that old errors disappear whenever the user tries again.

  • Open your browser's DevTools by pressing Cmd+Option+I (macOS) or Ctrl+Shift+I (Windows).
  • Click the Network tab at the top of DevTools.
  • Check the Offline checkbox to simulate a network failure.
  • Click the Get Advice button. You should see a red error message that says "Something went wrong. Please try again!" appear on the card.
  • Uncheck the Offline checkbox to restore your connection.
  • Click Get Advice again. The error message disappears and fresh advice loads in its place.

How does the error disappear automatically?

The errorMessageElement.classList.remove('visible') line runs at the very start of every fetch attempt. This means any leftover error from a previous failure gets hidden the moment the user clicks the button again. If the new fetch succeeds, the error stays hidden. If it fails again, the catch block makes it reappear.

✔️ Awesome, I've got everything!

Great. Double check you've saved script.js before moving on.

ⓧ I'd like to double check the full code

Here's the complete script.js file at this point:

const adviceTextElement = document.getElementById('advice-text');
const adviceIdElement = document.getElementById('advice-id');
const errorMessageElement = document.getElementById('error-message');
const loadingElement = document.getElementById('loading');
const getAdviceBtn = document.getElementById('get-advice-btn');

async function fetchAdvice() {
    loadingElement.classList.add('visible');
    errorMessageElement.classList.remove('visible');
    getAdviceBtn.disabled = true;

    try {
        const response = await fetch('https://api.adviceslip.com/advice');

        if (!response.ok) {
            throw new Error(`Response status: ${response.status}`);
        }

        const data = await response.json();

        adviceIdElement.textContent = `Advice #${data.slip.slip_id}`;
        adviceTextElement.textContent = `"${data.slip.advice}"`;
    } catch (error) {
        errorMessageElement.textContent = 'Something went wrong. Please try again!';
        errorMessageElement.classList.add('visible');
    } finally {
        loadingElement.classList.remove('visible');
        getAdviceBtn.disabled = false;
    }
}

getAdviceBtn.addEventListener('click', fetchAdvice);

Secret mission

Add a Search Feature

Your advice generator works great for random discoveries. But what if someone wants advice about a specific topic? The Advice Slip API has a search endpoint that lets you find advice by keyword. Your mission: build a search feature that lets users type a keyword and see all matching advice.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally in your browser, so there are no ongoing costs. Decide whether to keep your project files for future reference or delete them entirely.

Resources you used:

  • Project folder containing index.html, style.css, and script.js.
  • Cursor (code editor).

Keep everything running

No action needed. Choose this if you want to keep experimenting with your advice generator or use it as a reference for future JavaScript projects.

  • Your project files stay on your Desktop, ready to open in Cursor or your browser anytime.
  • The Advice Slip API is free with no usage limits, so you can keep fetching advice without any cost.

Pause - I'll come back to this later

Close Cursor to free up memory. Your project files remain on your Desktop for when you return.

  • Close any browser tabs that have your advice generator open.
  • Close Cursor. Your files are saved and will still be there next time you open the folder.

Delete - I don't want to use this again

Remove all project files from your computer. Cursor stays installed for other projects unless you choose to uninstall it.

  • Close Cursor if it is still open.
  • Delete your project folder from your Desktop by dragging it to the Trash (macOS) or right-clicking and selecting Delete (Windows).
  • Empty your Trash (macOS) or Recycle Bin (Windows) to permanently remove the files.

Nice Work!

Nice Work!

You just built your first API-powered web app with vanilla JavaScript!

You've learned how to:

  • Built a complete HTML/CSS/JS project from scratch, with structured markup, modern styling, and interactive logic all working together.
  • Used the Fetch API with async/await to get live data from the internet and parsed the JSON response into usable content.
  • Manipulated the DOM to display dynamic content, and handled loading states and errors gracefully so users always know what's happening.
  • Added a search feature using a different API endpoint and rendered multiple results in a scrollable list.

Ready to quiz yourself?