Build a Content Workflow Dashboard

Build a responsive dashboard that creates, moves, and deletes content cards.

Introduction

30 Second Summary

Content ideas can pile up quickly when their progress lives in scattered notes. A clear workflow shows what needs attention at a glance.

In this project, you will build a browser-based content dashboard for planning work from idea to publication. Each content card moves through three stages with accurate totals throughout.

What You'll Build

Your finished dashboard turns a new content idea into a card that travels from Ideas to Published with accurate totals throughout.

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

  • An add-content form built with Semantic HTML that places each titled item in your chosen workflow stage.
  • A responsive CSS Grid board that keeps the three workflow stages readable on wide screens. The columns stack cleanly on narrow screens.
  • Interactive cards powered by vanilla JavaScript that advance from one stage to the next. Each move updates the visible totals immediately.
  • Secret Mission: Add a Delete button that removes any card while keeping its column count accurate.

Are there any prerequisites?

You do not need prior web development experience. The project includes availability checks for Visual Studio Code plus Microsoft Edge before you begin.

Before We Start

A clear audience gives your dashboard a purpose before the hands-on work begins. Take a moment to define who moves content through this workflow and what they need to publish.

Verify Your Workspace

Your dashboard now has a clear audience. It also has a purpose that follows content from idea to publication.

Before you build the dashboard, you need a verified path from Visual Studio Code to Microsoft Edge. This step also proves that the browser receives your stylesheet plus your JavaScript file.

In this step, get ready to:
  • Confirm that your required desktop tools are available.
  • Open the existing project folder in Visual Studio Code.
  • Prove that Edge loads all three project files.
Confirm your tools and project files

Your editor holds the project files. Your browser turns those files into the dashboard you can see and use.

  • Press the Windows key to open Windows Search.
  • Type Visual Studio Code.
  • Check whether Visual Studio Code appears in the search results.
  • Press the Windows key again.
  • Type Microsoft Edge.
  • Check whether Microsoft Edge appears in the search results.

✔️ Both apps are available

Good. Your editor and browser are ready for the project.

ⓧ One or both apps are missing

Install only the app that is missing. These installers guide you through the required Windows setup.

  • Visit the official Visual Studio Code Windows setup page if Visual Studio Code is missing.
  • Choose the Windows User setup.
  • Run the downloaded Visual Studio Code installer.
  • Complete the installer using its default options.
  • Visit the official Microsoft Edge download page if Edge is missing.
  • Download the Windows installer from the page.
  • Run the downloaded Microsoft Edge installer.
  • Complete the installer using its default options.

Windows may display a security prompt when an installer starts. Approve the prompt after confirming that the installer came from the official page above.

Still unable to open an app?

Restart Windows Search after the installation finishes. Search for the full product name again.

If the app still does not appear, help me troubleshoot the Windows installation.

  • Press the Windows key to open Windows Search.
  • Type Visual Studio Code.
  • Select Visual Studio Code from the search results.
  • Click File in the top menu bar.
  • Click Open Folder....
  • Choose the existing project folder in the folder picker.
  • Confirm your folder selection.
  • Check the Explorer sidebar for index.html, Styles.css, and Script.js.

You should see all three files in the Explorer sidebar. This confirms that Visual Studio Code has opened the correct folder.

Missing one of the project files?

Use File followed by Open Folder... again. Choose the folder that already contains all three files.

If the files still do not appear, help me find the correct project folder.

Connect the page and stylesheet

HTML gives the browser a page structure. The two file references in the page head connect that structure to CSS plus JavaScript.

  • Select index.html in the Visual Studio Code Explorer.
  • Replace the existing contents of index.html with this code:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Content Workflow Dashboard</title>
<link href="Styles.css" rel="stylesheet" />
<script src="Script.js" defer></script>
</head>
<body>
<h1>Content Workflow Dashboard</h1>
<button id="check-button" type="button">Check JavaScript</button>
<p id="connection-status">Waiting</p>
</body>
</html>

What does this page do?

  • The link element loads the styles from Styles.css.
  • The script element loads Script.js after the page has been parsed because it uses defer.
  • The check-button button provides a visible way to test JavaScript.
  • The connection-status paragraph starts with the text Waiting.
  • Save index.html by pressing Ctrl+S.
  • Press the Windows key to open Windows Search.
  • Type Microsoft Edge.
  • Select Microsoft Edge from the search results.
  • Press Ctrl+O to open a file from your computer.
  • Select index.html from the existing project folder.
  • Press Enter to load the file.

You should see the heading Content Workflow Dashboard. You should also see the Check JavaScript button above the Waiting status.

✔️ Awesome, I've got everything!

Great. Your saved HTML now loads as a local page in Edge.

ⓧ I'd like to double check the full code

Compare your complete index.html file with this reference.

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Content Workflow Dashboard</title>
<link href="Styles.css" rel="stylesheet" />
<script src="Script.js" defer></script>
</head>
<body>
<h1>Content Workflow Dashboard</h1>
<button id="check-button" type="button">Check JavaScript</button>
<p id="connection-status">Waiting</p>
</body>
</html>

Page missing the temporary controls?

Confirm that Edge opened index.html from the same folder shown in the Visual Studio Code Explorer. Save the file again if its editor tab shows an unsaved change.

If the page still looks different, help me check which local HTML file Edge opened.

The status color gives you a second connection check. Red represents the waiting state before JavaScript responds.

  • Select Styles.css in the Visual Studio Code Explorer.
  • Replace the existing contents of Styles.css with this code:
#connection-status{color:#b91c1c}#connection-status.connected{color:#15803d}

What does this style do?

  • The first selector makes the default connection status red.
  • The second selector makes the status green after JavaScript adds the connected class.
  • The color change makes the file connection visible without opening developer tools.
  • Save Styles.css by pressing Ctrl+S.
  • Return to the Edge window from earlier.
  • Press Ctrl+R to reload the page.

You should see Waiting in red. This proves that Edge loaded Styles.css through the link in index.html.

✔️ Awesome, I've got everything!

Your stylesheet is connected. The temporary status now has its waiting-state color.

ⓧ I'd like to double check the full code

Compare your complete Styles.css file with this reference.

#connection-status{color:#b91c1c}#connection-status.connected{color:#15803d}

Waiting text still has the default color?

Check that the filename is exactly Styles.css. Its capital S must match the href value in index.html.

If the style still does not load, help me debug the stylesheet link.

Prove JavaScript is running

The DOM represents the elements in your page. JavaScript can select those elements plus respond when you interact with them.

  • Select Script.js in the Visual Studio Code Explorer.
  • Replace the existing contents of Script.js with this code:
const checkButton=document.querySelector("#check-button");
const connectionStatus=document.querySelector("#connection-status");
checkButton.addEventListener("click",()=>{connectionStatus.textContent="Connected";connectionStatus.classList.add("connected");});

What does this code do?

  • The first two selections find the button plus the status paragraph using their element IDs.
  • The click listener waits for you to choose the button.
  • The listener changes the status text to Connected.
  • The listener adds the connected class so the stylesheet changes the status to green.
  • Save Script.js by pressing Ctrl+S.
  • Return to the Edge window from earlier.
  • Press Ctrl+R to reload the saved files.

Before you click the button, what change do you expect the text plus its color to make?

  • Click Check JavaScript.

You should see the status change from Waiting to Connected. The text should also change from red to green.

That confirms the full connection. Edge is now loading your HTML structure plus your stylesheet plus your deferred JavaScript.

✔️ Awesome, I've got everything!

Excellent. Your temporary connection check proves that all three project files work together.

ⓧ I'd like to double check the full code

Compare your complete Script.js file with this reference.

const checkButton=document.querySelector("#check-button");
const connectionStatus=document.querySelector("#connection-status");
checkButton.addEventListener("click",()=>{connectionStatus.textContent="Connected";connectionStatus.classList.add("connected");});

Button not changing the status?

Check that Script.js uses the selectors #check-button plus #connection-status. Both must match the IDs in index.html exactly.

Confirm that the script element includes defer. This lets the page elements exist before the script selects them.

If the button still does not respond, help me debug the temporary JavaScript connection check.

Your workspace is ready to support a real interface. Next, you will replace this temporary check with the static form plus the three workflow columns.

Build the Static Workflow Board

Your temporary check proved that the page loads its stylesheet. It also proved that JavaScript can respond to a click.

A static board gives you a visible baby version before interaction enters the picture. In this step, Semantic HTML will turn the test page into a labeled form with three workflow stages.

In this step, get ready to:
  • Replace the temporary page with a labeled content form.
  • Add three workflow columns with one sample card in each column.
  • Test the static form's default submission behavior.
Build the semantic dashboard structure

The page needs clear regions for its introduction, form, workflow columns, and content cards. Matching labels also connect each form control to a visible description.

  • Switch back to the index.html tab in Visual Studio Code.
  • Replace all existing code in index.html by pasting this complete page:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Content Workflow Dashboard</title>
<link href="Styles.css" rel="stylesheet" />
<script src="Script.js" defer></script>
</head>
<body>
<header class="site-header"><p class="eyebrow">Creator workspace</p><h1>Content Workflow Dashboard</h1><p>Plan, produce, and publish your next pieces of content.</p></header>
<main class="dashboard">
<section class="content-form-panel" aria-labelledby="new-content-title"><h2 id="new-content-title">Add content</h2><form id="content-form" class="content-form"><div class="form-field"><label for="content-title">Content title</label><input id="content-title" name="contentTitle" type="text" required /></div><div class="form-field"><label for="content-type">Content type</label><select id="content-type" name="contentType"><option value="Blog post">Blog post</option><option value="Video">Video</option><option value="Email">Email</option><option value="Social post">Social post</option></select></div><div class="form-field"><label for="content-status">Starting status</label><select id="content-status" name="contentStatus"><option value="ideas">Ideas</option><option value="in-progress">In Progress</option><option value="published">Published</option></select></div><button class="submit-button" type="submit">Add content</button></form></section>
<section id="workflow-board" class="workflow-board" aria-label="Content workflow">
<section class="workflow-column" data-column="ideas"><header class="column-header"><h2>Ideas</h2><span class="count">1</span></header><div class="card-list"><article class="content-card" data-status="ideas"><h3>Beginner content calendar</h3><p class="content-type">Blog post</p><div class="card-actions"><button class="advance-button" type="button">Move to In Progress</button></div></article></div></section>
<section class="workflow-column" data-column="in-progress"><header class="column-header"><h2>In Progress</h2><span class="count">1</span></header><div class="card-list"><article class="content-card" data-status="in-progress"><h3>Behind-the-scenes video</h3><p class="content-type">Video</p><div class="card-actions"><button class="advance-button" type="button">Move to Published</button></div></article></div></section>
<section class="workflow-column" data-column="published"><header class="column-header"><h2>Published</h2><span class="count">1</span></header><div class="card-list"><article class="content-card" data-status="published"><h3>Launch announcement</h3><p class="content-type">Social post</p><div class="card-actions"><button class="advance-button" type="button" disabled>Published</button></div></article></div></section>
</section></main></body></html>

What does this HTML do?

  • The <header> introduces the dashboard before the main workspace.
  • Each label's for value matches its control's id value.
  • The name values identify the form data that JavaScript can read later.
  • Each data-column value identifies a workflow destination.
  • Each sample card uses data-status to record its current stage.
  • Save index.html.
  • Return to the dashboard in Microsoft Edge.
  • Refresh the page.

You'll see the add-content form above Ideas, In Progress, and Published. Each workflow column contains one sample card with a count of 1.

You now have the first complete dashboard view in your browser. Every stage of the content workflow is visible.

Dashboard markup missing?

  • Confirm that you replaced all of the temporary markup in index.html.
  • Check that the file ends with </html>.
  • Save index.html before refreshing the browser.

Ask for help comparing the structure with the target file: Why is part of my static dashboard missing in Microsoft Edge?

Clear the temporary setup code

The old connection styles target elements that the new page no longer contains. A small CSS baseline will keep the form readable while showing where each workflow stage begins.

  • Switch back to the Styles.css tab.
  • Replace all existing code in Styles.css by pasting these structural styles:
body{font-family:system-ui,sans-serif;margin:0}.dashboard{padding:1rem}.workflow-column{margin:1rem 0;padding:1rem;border:1px solid #ccc}.content-form{display:block}.form-field{margin-bottom:1rem}

What do these styles do?

  • The body rule applies a system font across the page.
  • The .dashboard rule adds space around the main workspace.
  • The .workflow-column rule separates each stage with spacing and a border.
  • The .form-field rule adds space between the labeled controls.
  • Save Styles.css.
  • Return to the dashboard in Microsoft Edge.
  • Refresh the page.

You'll see padding around the dashboard. Each workflow column now has its own border.

Structural styles missing?

  • Confirm that the stylesheet is named exactly Styles.css.
  • Check that index.html still links to Styles.css.
  • Save the stylesheet before refreshing the page.

Ask for help tracing the stylesheet connection: Why are the dashboard spacing and column borders missing?

The remaining temporary listener looks for the deleted button and status message. Clearing it gives the static dashboard a clean behavior baseline.

  • Switch back to the Script.js tab.
  • Press Ctrl+A to select the temporary listener.
  • Press Backspace to remove the selected code.

The Script.js editor is now empty.

  • Save Script.js.
  • Return to the dashboard in Microsoft Edge.
  • Refresh the page.

You'll still see the form and all three workflow columns. This proves that the static dashboard no longer depends on the temporary listener.

Temporary check still appearing?

  • Confirm that Script.js contains no code.
  • Check that index.html contains no check-button element.
  • Refresh Microsoft Edge after saving every file.

Ask for help locating the remaining test code: Why does the old connection check still appear?

✔️ Awesome, I've got everything!

Your static dashboard is saved. The temporary connection check is fully removed.

ⓧ I'd like to double check the full code

  • Compare your saved index.html with this complete reference:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Content Workflow Dashboard</title>
<link href="Styles.css" rel="stylesheet" />
<script src="Script.js" defer></script>
</head>
<body>
<header class="site-header"><p class="eyebrow">Creator workspace</p><h1>Content Workflow Dashboard</h1><p>Plan, produce, and publish your next pieces of content.</p></header>
<main class="dashboard">
<section class="content-form-panel" aria-labelledby="new-content-title"><h2 id="new-content-title">Add content</h2><form id="content-form" class="content-form"><div class="form-field"><label for="content-title">Content title</label><input id="content-title" name="contentTitle" type="text" required /></div><div class="form-field"><label for="content-type">Content type</label><select id="content-type" name="contentType"><option value="Blog post">Blog post</option><option value="Video">Video</option><option value="Email">Email</option><option value="Social post">Social post</option></select></div><div class="form-field"><label for="content-status">Starting status</label><select id="content-status" name="contentStatus"><option value="ideas">Ideas</option><option value="in-progress">In Progress</option><option value="published">Published</option></select></div><button class="submit-button" type="submit">Add content</button></form></section>
<section id="workflow-board" class="workflow-board" aria-label="Content workflow">
<section class="workflow-column" data-column="ideas"><header class="column-header"><h2>Ideas</h2><span class="count">1</span></header><div class="card-list"><article class="content-card" data-status="ideas"><h3>Beginner content calendar</h3><p class="content-type">Blog post</p><div class="card-actions"><button class="advance-button" type="button">Move to In Progress</button></div></article></div></section>
<section class="workflow-column" data-column="in-progress"><header class="column-header"><h2>In Progress</h2><span class="count">1</span></header><div class="card-list"><article class="content-card" data-status="in-progress"><h3>Behind-the-scenes video</h3><p class="content-type">Video</p><div class="card-actions"><button class="advance-button" type="button">Move to Published</button></div></article></div></section>
<section class="workflow-column" data-column="published"><header class="column-header"><h2>Published</h2><span class="count">1</span></header><div class="card-list"><article class="content-card" data-status="published"><h3>Launch announcement</h3><p class="content-type">Social post</p><div class="card-actions"><button class="advance-button" type="button" disabled>Published</button></div></article></div></section>
</section></main></body></html>

What should match in the HTML?

This reference contains the complete form and all three sample workflow cards. It also keeps the existing stylesheet and deferred script links.

  • Compare your saved Styles.css with this complete reference:
body{font-family:system-ui,sans-serif;margin:0}.dashboard{padding:1rem}.workflow-column{margin:1rem 0;padding:1rem;border:1px solid #ccc}.content-form{display:block}.form-field{margin-bottom:1rem}

What should match in the CSS?

This reference contains the complete structural stylesheet for the current board. The connection-status selectors are gone.

Your Script.js file should be completely empty. The temporary click listener has no place in the static dashboard.

Test the form's default behavior

The static form can collect a title, type, and starting status. This check reveals whether the current page can turn those values into a new card.

Before you submit, do you think the new title will remain in the Ideas column?

  • Enter Test idea in the Content title field.
  • Click Add content.

You'll see the page reload without adding Test idea to the Ideas column. The Ideas count remains 1.

Why was no card created?

The browser performs the form's default submission behavior. The static document has no instruction for creating a card from the submitted values.

This shortfall is intentional. You have proved the exact behavior that the interactive version needs to add.

Your static workflow board is complete. Next, you'll use CSS Grid to turn its vertical structure into a responsive dashboard.

Style the Dashboard

Your semantic HTML already shows the form and three workflow stages. The plain layout makes those stages difficult to scan.

CSS Grid gives each stage a clear place on wide screens. Responsive design keeps the same dashboard readable when the browser becomes narrow.

In this step, get ready to:
  • Style the dashboard's visual foundation.
  • Arrange the form and workflow with CSS Grid.
  • Stack both layouts on narrow screens.
Build the visual foundation

A shared color system keeps the page consistent. Global sizing rules also make form controls easier to size predictably.

  • In the Explorer sidebar in Visual Studio Code, select Styles.css.
  • Press Cmd+A (macOS) or Ctrl+A (Windows) to select the existing styles.
  • Replace the selected styles by pasting this code:
:root{color:#172033;background:#f3f6fb;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh;background:#f3f6fb}button,input,select{font:inherit}button{cursor:pointer}button:focus-visible,input:focus-visible,select:focus-visible{outline:3px solid #f59e0b;outline-offset:2px}

What Do These Styles Do?

  • The :root rule establishes the page colors and font stack.
  • The universal box-sizing rule keeps padding and borders inside each element's declared size.
  • The :focus-visible selectors add an orange outline when someone navigates through the controls with a keyboard.
  • Save Styles.css by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Return to the dashboard in Microsoft Edge.
  • Refresh the page by pressing Cmd+R (macOS) or Ctrl+R (Windows).
  • Press Tab until a form control receives focus.

Good start. You'll see a pale page background with updated typography. The focused control has a visible orange outline.

Styles Not Changing?

  • Confirm that the selected file is named exactly Styles.css.
  • Check the Styles.css tab for a dot that indicates unsaved changes.

Help me work out why my Styles.css changes are not appearing.

The global rules establish the visual baseline. The header and form panel can now create a clear entry point for the workflow.

  • Switch back to Styles.css.
  • Place the cursor immediately after the final closing brace.
  • Add the header and panel styling by pasting this code:
.site-header{padding:2.5rem clamp(1rem,4vw,4rem);color:white;background:linear-gradient(135deg,#1d4ed8,#6d28d9)}.site-header h1,.site-header p{margin:0}.site-header h1{margin-block:.35rem}.eyebrow{font-size:.8rem;font-weight:800;letter-spacing:.12em;text-transform:uppercase}.dashboard{width:min(1180px,calc(100% - 2rem));margin:2rem auto}.content-form-panel{padding:1.25rem;margin-bottom:1.5rem;background:white;border:1px solid #dbe3f0;border-radius:1rem;box-shadow:0 10px 30px rgb(31 41 55 / .08)}.content-form-panel h2{margin-top:0}

How Does the Page Take Shape?

  • The .site-header rule creates the gradient banner with responsive horizontal padding.
  • The .dashboard rule limits the content width while keeping it centered.
  • The .content-form-panel rule turns the form area into a raised white panel.
  • Save Styles.css.
  • Return to Microsoft Edge.
  • Refresh the dashboard.

You'll see a blue-to-purple header above a centered white form panel. The panel now stands apart from the page background.

Header Still Looks Plain?

  • Confirm that the new code begins immediately after the previous closing brace.
  • Check that .site-header includes its leading full stop.

Help me find why the site header or form panel styles are missing.

The panel has a clear boundary now. The controls inside it need their own spacing and alignment so the form reads as one unit.

  • Switch back to Styles.css.
  • Place the cursor immediately after the final closing brace.
  • Add the form layout and control styling by pasting this code:
.content-form{display:grid;grid-template-columns:2fr 1fr 1fr auto;gap:1rem;align-items:end}.form-field{display:grid;gap:.4rem}.form-field label{font-weight:700}.form-field input,.form-field select,.submit-button{min-height:44px;padding:.7rem .8rem;border:1px solid #aab7ca;border-radius:.65rem}.submit-button{color:white;background:#1d4ed8;border-color:#1d4ed8;font-weight:800}

What Does the Form Grid Control?

  • The 2fr 1fr 1fr auto tracks give the title field the most room.
  • The align-items rule lines up the controls along their bottom edges.
  • Shared control styles give the input, selects, and submit button consistent sizing.
  • Save Styles.css.
  • Return to Microsoft Edge.
  • Refresh the dashboard.

On a wide window, you'll see the title field, two selects, and blue submit button aligned in one row.

Form Controls Not Lining Up?

  • Confirm that .content-form uses display:grid.
  • Check that every .form-field rule has its leading full stop.

Help me troubleshoot why my content form is not displaying as a grid.

Lay out the workflow with CSS Grid

The workflow stages represent parallel pieces of the same process. Equal grid columns make that relationship visible without assigning fixed widths to each section.

  • Switch back to Styles.css.
  • Place the cursor immediately after the final closing brace.
  • Add the board layout and column styling by pasting this code:
.workflow-board{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem}.workflow-column{min-width:0;padding:1rem;background:#e8edf5;border-radius:1rem}.column-header{display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-bottom:1rem}.column-header h2{margin:0;font-size:1.05rem}.count{display:inline-grid;min-width:2rem;min-height:2rem;place-items:center;color:white;background:#334155;border-radius:999px;font-weight:800}

Why CSS Grid Here?

CSS Grid defines all three workflow tracks from one rule. The browser distributes the available width evenly across them.

  • The repeat(3,minmax(0,1fr)) value creates three equal columns that can shrink without forcing overflow.
  • The .workflow-column rule gives each stage a separate panel.
  • The .count rule presents each total as a circular badge.
  • Save Styles.css.
  • Return to Microsoft Edge.
  • Refresh the dashboard.

You'll see Ideas, In Progress, and Published arranged as three equal panels. Each heading now has a dark circular count badge.

Columns Still Stacked?

  • Confirm that the Microsoft Edge window is wide enough to display the three columns.
  • Check the spelling of .workflow-board against the class in index.html.

Help me diagnose why my workflow board is not showing three grid columns.

The three workflow stages now have structure. Card surfaces and action states make the individual content items easier to identify.

  • Switch back to Styles.css.
  • Place the cursor immediately after the final closing brace.
  • Add the card and button styling by pasting this code:
.card-list{display:grid;gap:.85rem}.content-card{padding:1rem;background:white;border-left:5px solid #7c3aed;border-radius:.75rem;box-shadow:0 6px 18px rgb(31 41 55 / .08)}.content-card h3{margin:0 0 .5rem;font-size:1rem}.content-type{margin:0 0 1rem;color:#526079}.card-actions{display:flex;gap:.5rem}.advance-button{width:100%;min-height:40px;padding:.55rem .7rem;color:#1d4ed8;background:#eff6ff;border:1px solid #93c5fd;border-radius:.55rem;font-weight:800}.advance-button:disabled{color:#64748b;background:#e2e8f0;border-color:#cbd5e1;cursor:not-allowed}

How Do the Cards Show State?

  • The .content-card rule creates a white card with a purple status edge.
  • The .advance-button rule makes the workflow action fill the available card width.
  • The :disabled rule gives the Published action a muted appearance.
  • Save Styles.css.
  • Return to Microsoft Edge.
  • Refresh the dashboard.

The sample items now appear as separate white cards with purple edges. Ideas and In Progress have blue action buttons. Published has a muted disabled button.

Cards Missing Their Styling?

  • Confirm that .content-card includes a leading full stop.
  • Check that the button rule uses the existing .advance-button class.

Help me find why my content cards or advance buttons are not receiving their CSS styles.

Make the dashboard responsive

A media query applies different layout rules when the viewport reaches a chosen width. This breakpoint turns both grids into one-column layouts on smaller screens.

  • Switch back to Styles.css.
  • Place the cursor immediately after the final closing brace.
  • Add the responsive breakpoint by pasting this code:
@media(max-width:900px){.content-form,.workflow-board{grid-template-columns:1fr}}

What Does the Breakpoint Change?

  • The media query activates when the viewport is at or below 900px wide.
  • The .content-form selector changes its controls to one column.
  • The .workflow-board selector stacks the three workflow stages vertically.
  • Save Styles.css.
  • Before you resize the window, predict whether the workflow board will keep three columns or collapse into one.
  • Return to Microsoft Edge.
  • Refresh the dashboard.
  • Resize the Microsoft Edge window until it is wider than 900px.
  • Resize the window until it is narrower than 900px.

On the wide window, you'll see three workflow columns side by side. On the narrow window, you'll see the form controls and workflow columns stacked into single-column layouts.

  • Press Tab until the keyboard focus reaches an input, select, or button.

You'll see a clear orange outline around the focused control. That's the visual confirmation that keyboard focus remains visible.

Dashboard Not Stacking?

  • Confirm that the media query appears at the very end of Styles.css.
  • Check that both .content-form and .workflow-board appear inside the media query.
  • Make the Microsoft Edge window narrower than 900px before checking the stacked layout.

Help me troubleshoot why my dashboard does not collapse to one column on a narrow screen.

✔️ Awesome, I've got everything!

Excellent. Your styled dashboard now adapts to wide and narrow browser windows.

ⓧ I'd like to double check the full code

  • Compare your complete Styles.css file with this reference:
:root{color:#172033;background:#f3f6fb;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}*{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh;background:#f3f6fb}button,input,select{font:inherit}button{cursor:pointer}button:focus-visible,input:focus-visible,select:focus-visible{outline:3px solid #f59e0b;outline-offset:2px}.site-header{padding:2.5rem clamp(1rem,4vw,4rem);color:white;background:linear-gradient(135deg,#1d4ed8,#6d28d9)}.site-header h1,.site-header p{margin:0}.site-header h1{margin-block:.35rem}.eyebrow{font-size:.8rem;font-weight:800;letter-spacing:.12em;text-transform:uppercase}.dashboard{width:min(1180px,calc(100% - 2rem));margin:2rem auto}.content-form-panel{padding:1.25rem;margin-bottom:1.5rem;background:white;border:1px solid #dbe3f0;border-radius:1rem;box-shadow:0 10px 30px rgb(31 41 55 / .08)}.content-form-panel h2{margin-top:0}.content-form{display:grid;grid-template-columns:2fr 1fr 1fr auto;gap:1rem;align-items:end}.form-field{display:grid;gap:.4rem}.form-field label{font-weight:700}.form-field input,.form-field select,.submit-button{min-height:44px;padding:.7rem .8rem;border:1px solid #aab7ca;border-radius:.65rem}.submit-button{color:white;background:#1d4ed8;border-color:#1d4ed8;font-weight:800}.workflow-board{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem}.workflow-column{min-width:0;padding:1rem;background:#e8edf5;border-radius:1rem}.column-header{display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-bottom:1rem}.column-header h2{margin:0;font-size:1.05rem}.count{display:inline-grid;min-width:2rem;min-height:2rem;place-items:center;color:white;background:#334155;border-radius:999px;font-weight:800}.card-list{display:grid;gap:.85rem}.content-card{padding:1rem;background:white;border-left:5px solid #7c3aed;border-radius:.75rem;box-shadow:0 6px 18px rgb(31 41 55 / .08)}.content-card h3{margin:0 0 .5rem;font-size:1rem}.content-type{margin:0 0 1rem;color:#526079}.card-actions{display:flex;gap:.5rem}.advance-button{width:100%;min-height:40px;padding:.55rem .7rem;color:#1d4ed8;background:#eff6ff;border:1px solid #93c5fd;border-radius:.55rem;font-weight:800}.advance-button:disabled{color:#64748b;background:#e2e8f0;border-color:#cbd5e1;cursor:not-allowed}@media(max-width:900px){.content-form,.workflow-board{grid-template-columns:1fr}}

Your dashboard is now structured for quick scanning across screen sizes. Next, you'll make the form create real content cards instead of using the browser's default submission behavior.

Turn Form Submissions into Cards

Your responsive dashboard already makes the workflow easy to scan. The form still reloads without adding anything to the board.

JavaScript now needs to intercept that submission before the browser clears the attempt. It can then turn the submitted values into a new card in the DOM.

In this step, get ready to:
  • Connect the existing form and workflow board to JavaScript.
  • Build cards from submitted field values.
  • Reset the form and synchronize every column count.
Build the submission workflow

A submit listener gives JavaScript control before the browser performs the form's default action. The handler can read each named control once it has that control.

  • In the Explorer sidebar in Visual Studio Code, select Script.js.
  • Build the complete form-to-card flow by pasting this code into the empty file:
const contentForm=document.querySelector("#content-form");const workflowBoard=document.querySelector("#workflow-board");const statuses=["ideas","in-progress","published"];const nextStatus={ideas:"in-progress","in-progress":"published"};const actionLabels={ideas:"Move to In Progress","in-progress":"Move to Published",published:"Published"};function getCardList(status){return document.querySelector(`[data-column="${status}"] .card-list`);}function createAdvanceButton(status){const button=document.createElement("button");button.className="advance-button";button.type="button";button.textContent=actionLabels[status];button.disabled=status==="published";return button;}function createContentCard(title,type,status){const card=document.createElement("article");card.className="content-card";card.dataset.status=status;const heading=document.createElement("h3");heading.textContent=title;const typeLabel=document.createElement("p");typeLabel.className="content-type";typeLabel.textContent=type;const actions=document.createElement("div");actions.className="card-actions";actions.append(createAdvanceButton(status));card.append(heading,typeLabel,actions);return card;}function updateCounts(){statuses.forEach((status)=>{const list=getCardList(status);const count=document.querySelector(`[data-column="${status}"] .count`);count.textContent=String(list.childElementCount);});}contentForm.addEventListener("submit",(event)=>{event.preventDefault();const formData=new FormData(contentForm);const title=formData.get("contentTitle");const type=formData.get("contentType");const status=formData.get("contentStatus");if(typeof title!=="string"||typeof type!=="string"||typeof status!=="string"||title.trim()===""){return;}const card=createContentCard(title.trim(),type,status);getCardList(status).append(card);contentForm.reset();updateCounts();});updateCounts();

How does the workflow fit together?

  • The contentForm reference points to the form that sends each submission.
  • The workflowBoard reference points to the three workflow columns.
  • The statuses, nextStatus, and actionLabels values describe the workflow stages.
  • The getCardList helper finds the card list for a specific status.
  • The createAdvanceButton helper gives each card the correct action button. Published cards receive a disabled button.
  • The createContentCard helper creates the article elements for one card. Its textContent assignments place the submitted text into those elements.
  • The updateCounts helper reads childElementCount from every card list.
  • The submit listener calls preventDefault() before the browser reloads the page.
  • The FormData object reads contentTitle, contentType, and contentStatus from the named controls.
  • The type check stops incomplete values from reaching the card factory.
  • The selected card list receives the finished card.
  • The form resets after a successful submission.
  • The final count refresh keeps the visible totals aligned with the cards.
  • Press Cmd+S (macOS) or Ctrl+S (Windows) to save Script.js.
  • Return to the dashboard in Microsoft Edge.
  • Refresh the page.

You'll see the three original sample cards in their existing columns. Each visible count shows 1 after updateCounts reads the card lists during page load.

✔️ Awesome, I've got everything!

Your Script.js file is saved with the complete card-creation workflow.

ⓧ I'd like to double check the full code

const contentForm=document.querySelector("#content-form");const workflowBoard=document.querySelector("#workflow-board");const statuses=["ideas","in-progress","published"];const nextStatus={ideas:"in-progress","in-progress":"published"};const actionLabels={ideas:"Move to In Progress","in-progress":"Move to Published",published:"Published"};function getCardList(status){return document.querySelector(`[data-column="${status}"] .card-list`);}function createAdvanceButton(status){const button=document.createElement("button");button.className="advance-button";button.type="button";button.textContent=actionLabels[status];button.disabled=status==="published";return button;}function createContentCard(title,type,status){const card=document.createElement("article");card.className="content-card";card.dataset.status=status;const heading=document.createElement("h3");heading.textContent=title;const typeLabel=document.createElement("p");typeLabel.className="content-type";typeLabel.textContent=type;const actions=document.createElement("div");actions.className="card-actions";actions.append(createAdvanceButton(status));card.append(heading,typeLabel,actions);return card;}function updateCounts(){statuses.forEach((status)=>{const list=getCardList(status);const count=document.querySelector(`[data-column="${status}"] .count`);count.textContent=String(list.childElementCount);});}contentForm.addEventListener("submit",(event)=>{event.preventDefault();const formData=new FormData(contentForm);const title=formData.get("contentTitle");const type=formData.get("contentType");const status=formData.get("contentStatus");if(typeof title!=="string"||typeof type!=="string"||typeof status!=="string"||title.trim()===""){return;}const card=createContentCard(title.trim(),type,status);getCardList(status).append(card);contentForm.reset();updateCounts();});updateCounts();

This reference shows the exact finished contents of Script.js.

Verify card creation and counts

An end-to-end check proves that the selected status controls the card's destination. The updated count proves that the dashboard reads its current cards.

The same check confirms that the form resets after a successful submission.

Before you submit, ask yourself which column should receive the new card.

  • Enter Email welcome series in the Content title field.
  • Select Email in Content type.
  • Select In Progress in Starting status.
  • Click Add content.

You'll see Email welcome series beneath the original Behind-the-scenes video card. The In Progress count changes from 1 to 2.

The title field clears. The type control returns to Blog post.

The status control returns to Ideas. You closed the reload gap with a complete form-to-card flow.

Form still reloads or misses the card?

  • Confirm that the Script.js tab has no unsaved dot beside its name.
  • Check that index.html still contains <script src="Script.js" defer></script>.
  • Compare the capitalization of contentTitle, contentType, and contentStatus with the form's name values.
  • Confirm that contentForm.reset() appears after the new card is appended.
  • Confirm that updateCounts() appears after the form reset.

Help me debug my form submission workflow.

Your dashboard now creates cards from form submissions while keeping every count accurate. Next, you'll make those cards move through the workflow.

Move Content Through the Workflow

Your form now turns new entries into cards. A useful workflow also needs each card to progress from an idea to a published item.

One listener on the board can handle every advance button. This event delegation pattern also covers buttons on cards created later.

In this step, get ready to:
  • Use the existing status maps to determine each card's next destination.
  • Add one delegated board listener for advance-button clicks.
  • Verify that a card reaches Published with synchronized counts.
Trace the workflow rules

The existing nextStatus object describes the route between columns. The actionLabels object supplies the button text for each status.

  • Switch back to Script.js in Visual Studio Code.
  • Locate nextStatus near the top of the file.
  • Read the value for ideas to identify its next status.
  • Locate actionLabels directly below nextStatus.

The ideas status points to in-progress. The in-progress status points to published.

The published status has an action label with no next status. That makes it the workflow's stopping point.

Delegate clicks to the board

The listener uses closest to identify the clicked advance button. It then finds the button's nearest card.

The card's data attribute is available through dataset.status. The existing maps use that status to choose the next column and button label.

  • In Script.js, scroll to the final updateCounts(); line.
  • Insert the delegated listener immediately above that line by pasting this code:
workflowBoard.addEventListener("click",(event)=>{if(!(event.target instanceof Element)){return;}const button=event.target.closest(".advance-button");if(!button){return;}const card=button.closest(".content-card");const newStatus=nextStatus[card.dataset.status];if(!newStatus){return;}card.dataset.status=newStatus;button.textContent=actionLabels[newStatus];button.disabled=newStatus==="published";getCardList(newStatus).append(card);updateCounts();});

What does this listener do?

  • The addEventListener call gives workflowBoard one handler for every bubbled click.
  • The event.target instanceof Element check protects the calls to closest.
  • The card.dataset.status value retrieves the card's current workflow stage.
  • The append call moves the existing card into its next list. The updateCounts call recalculates every column total.
  • Save Script.js by pressing Ctrl+S.
  • Switch back to Microsoft Edge from earlier.
  • Refresh the dashboard with the browser's Refresh button.
  • Click Move to In Progress on the Beginner content calendar card.

You'll see the same card under In Progress. Its button now reads Move to Published.

The Ideas count becomes 0. The In Progress count becomes 2.

Button not moving the card?

  • Confirm that the new listener appears immediately above the final updateCounts(); line in Script.js.
  • Confirm that the selectors are spelled exactly as .advance-button and .content-card.
  • Save Script.js before refreshing the dashboard again.

Help me debug the workflow button.

✔️ Awesome, I've got everything!

Great. Your delegated listener is saved and moving cards between workflow columns.

ⓧ I'd like to double check the full code

  • Compare your saved Script.js with this complete version:
const contentForm=document.querySelector("#content-form");
const workflowBoard=document.querySelector("#workflow-board");
const statuses=["ideas","in-progress","published"];
const nextStatus={ideas:"in-progress","in-progress":"published"};
const actionLabels={ideas:"Move to In Progress","in-progress":"Move to Published",published:"Published"};
function getCardList(status){return document.querySelector(`[data-column="${status}"] .card-list`);}
function createAdvanceButton(status){const button=document.createElement("button");button.className="advance-button";button.type="button";button.textContent=actionLabels[status];button.disabled=status==="published";return button;}
function createContentCard(title,type,status){const card=document.createElement("article");card.className="content-card";card.dataset.status=status;const heading=document.createElement("h3");heading.textContent=title;const typeLabel=document.createElement("p");typeLabel.className="content-type";typeLabel.textContent=type;const actions=document.createElement("div");actions.className="card-actions";actions.append(createAdvanceButton(status));card.append(heading,typeLabel,actions);return card;}
function updateCounts(){statuses.forEach((status)=>{const list=getCardList(status);const count=document.querySelector(`[data-column="${status}"] .count`);count.textContent=String(list.childElementCount);});}
contentForm.addEventListener("submit",(event)=>{event.preventDefault();const formData=new FormData(contentForm);const title=formData.get("contentTitle");const type=formData.get("contentType");const status=formData.get("contentStatus");if(typeof title!=="string"||typeof type!=="string"||typeof status!=="string"||title.trim()===""){return;}const card=createContentCard(title.trim(),type,status);getCardList(status).append(card);contentForm.reset();updateCounts();});
workflowBoard.addEventListener("click",(event)=>{if(!(event.target instanceof Element)){return;}const button=event.target.closest(".advance-button");if(!button){return;}const card=button.closest(".content-card");const newStatus=nextStatus[card.dataset.status];if(!newStatus){return;}card.dataset.status=newStatus;button.textContent=actionLabels[newStatus];button.disabled=newStatus==="published";getCardList(newStatus).append(card);updateCounts();});
updateCounts();
Move the card to Published

The first click changed the card's status to in-progress. The same delegated listener can now process its updated state.

Before you click again, make a quick prediction about whether the same card moves or a new copy appears.

  • Click Move to Published on the Beginner content calendar card.

You'll see the same card under Published. Its button now reads Published.

The Published button is disabled. The Ideas count stays at 0.

The In Progress count returns to 1. The Published count rises to 2.

That's the workflow complete. One listener now moves sample cards and newly created cards through every stage.

Secret mission

Delete Content Cards

Content plans change as ideas become outdated or accidental cards appear. Extend your dashboard with a Delete button that removes any card while immediately correcting its column count.

Clean Up Your Resources

Clean Up Your Resources

Your dashboard runs entirely from local files in Microsoft Edge. It creates no ongoing cloud costs, whether you keep it open, pause your work, or delete the files.

Resources you used:

  • The index.html file that defines the semantic form and workflow board.
  • The Styles.css file that controls the dashboard's responsive presentation.
  • The Script.js file that keeps card actions and column counts synchronized.

Keep everything running

No action needed. Choose this if you are still testing the dashboard in Visual Studio Code or Microsoft Edge.

Your three files stay available in the existing project folder. The open tools use only your computer's local resources.

Pause - I'll come back to this later

Close the local dashboard and editor to free up memory while keeping your project files for another session.

  • Close the dashboard tab in Microsoft Edge.
  • Close Visual Studio Code.

Your index.html, Styles.css, and Script.js files remain in the existing project folder.

Delete - I don't want to use this again

This option permanently removes the three dashboard files from the existing project folder. Visual Studio Code and Microsoft Edge stay installed.

  • Close the dashboard tab in Microsoft Edge.
  • Switch back to the Explorer sidebar in Visual Studio Code.
  • Select index.html.
  • Hold Ctrl while selecting Styles.css.
  • Keep holding Ctrl while selecting Script.js.
  • Press the Delete key.
  • Confirm the deletion if a prompt appears.
  • Check the Explorer sidebar for the three filenames.

Cleanup is complete. The Explorer sidebar should no longer list index.html, Styles.css, or Script.js.

Nice Work!

Nice Work!

You did it! Your content workflow dashboard now carries cards from idea to publication.

You've learned how to:

  • Built an accessible form with semantic HTML. Organized content inside a visible three-stage workflow.
  • Used CSS Grid with a responsive media query. The board shifts from three columns to one as the browser narrows.
  • Created new cards from form submissions with DOM APIs. Used data attributes with event delegation to move each card. Kept every column count synchronized after each move.
  • Completed the Secret Mission by adding delegated card deletion. Each removal immediately corrects its column total.

Ready to quiz yourself?