Skip to content
nuggetof joy

Post · October 22, 2026

AMDE 355 – Interactive Web Experiences (Week 10)

JavaScript, without the mystery

JavaScript is the part that makes something happen

Think of a web page as a small stage:

  • HTML puts the things on the stage: headings, buttons, images, cards.
  • CSS decides how those things look: color, size, spacing, layout.
  • JavaScript makes things respond: open a panel, filter projects, update a message, or react when someone clicks.

You do not need to memorize JavaScript. Start by recognizing the same simple pattern each time:

The basic pattern
// 1. Find something on the page.
const button = document.querySelector("#hello-button");

// 2. Listen for an action.
button.addEventListener("click", function () {

  // 3. Make something happen.
  alert("You clicked the button!");

});

Read it in plain English: “Find the element with the ID hello-button. When someone clicks it, show an alert.”

For now, focus on the three jobs: find → listen → respond. Most of the beginner interactions in this lesson use that same pattern.

A tiny vocabulary list

What the JavaScript words mean

CodePlain-English meaning
const button = ...Give the thing we found a short name so we can use it again.
documentThe current web page.
querySelector("#menu")Find the first page element that matches this CSS selector. #menu means an element with id="menu".
addEventListener("click", ...)Wait for a click, then run the instructions inside.
function () { ... }The instructions to run when the event happens.
classList.toggle("open")If the element has the class, remove it. If it does not, add it.
textContentThe readable text inside an element.
if (...) { ... }“If this condition is true, do these instructions.”

Important: JavaScript is very literal. The ID in your HTML and the selector in your JavaScript must match exactly. #hello-button looks for id="hello-button"; it does not look for a class named hello-button.

Example 1 · One click, one response

Build the smallest useful interaction

First, write the HTML. The button needs an ID so JavaScript can find it.

HTML
<button id="hello-button">
  Say hello
</button>

Next, add the JavaScript. Place it near the end of the page, just before </body>, so the button already exists when the script runs.

JavaScript
const helloButton = document.querySelector("#hello-button");

helloButton.addEventListener("click", function () {
  alert("Hello, web designer!");
});

Try this: Change the button text and the alert message. Save, refresh the browser, and click the button again.

Why use a name like helloButton? The name is for you, the person reading the code. It makes the next line easier to understand. You could call it myButton, but choose a name that tells you what it represents.

Example 2 · Change the page

Make a button update text instead of showing a popup

This pattern is useful for “Show more,” “Read the artist statement,” or a simple confirmation message.

HTML
<p id="status-message">
  This is the original message.
</p>

<button id="change-message">
  Change message
</button>
JavaScript
const changeButton = document.querySelector("#change-message");
const message = document.querySelector("#status-message");

changeButton.addEventListener("click", function () {
  message.textContent = "The message has changed!";
});

Read it: We find two elements, listen for a click on the button, and replace the paragraph’s text.

Notice: textContent changes the text. It does not change the element’s CSS, its size, or its color. Those are separate jobs.

Example 3 · Open and close

Use a class to show or hide content

A common beginner approach is to use JavaScript to add or remove a CSS class. CSS controls what that class looks like; JavaScript decides when the class is applied.

HTML
<button id="details-button"
        aria-expanded="false"
        aria-controls="project-details">
  Show project details
</button>

<div id="project-details" class="details-panel" hidden>
  <p>This project explores color, movement, and memory.</p>
</div>
JavaScript
const detailsButton = document.querySelector("#details-button");
const detailsPanel = document.querySelector("#project-details");

detailsButton.addEventListener("click", function () {
  const isHidden = detailsPanel.hidden;

  detailsPanel.hidden = !isHidden;
  detailsButton.setAttribute("aria-expanded", isHidden);

  if (isHidden) {
    detailsButton.textContent = "Hide project details";
  } else {
    detailsButton.textContent = "Show project details";
  }
});

Plain English, line by line:

  1. Find the button and the content panel.
  2. When the button is clicked, check whether the panel is currently hidden.
  3. If it is hidden, show it. If it is visible, hide it.
  4. Update the button label so it describes the next action.
  5. Update aria-expanded so assistive technology can tell whether the content is open.

The symbol ! means “not.” If isHidden is true, !isHidden is false. This is how the code flips between hidden and visible.

When it does not work

Beginner debugging: check these things first

  1. Check the spelling. Does id="details-button" match querySelector("#details-button") exactly?
  2. Check the symbol. A class selector starts with a dot, like ".project-card". An ID selector starts with a hash, like "#details-button".
  3. Check where the script lives. Put the script just before </body> or use defer if the script is in the document head.
  4. Check the browser console. Right-click the page, choose Inspect, and open Console. Red error messages often point to the line with the problem.
  5. Check whether the element exists. If JavaScript cannot find the element, the variable may be null. This often means the selector is wrong or the script ran too early.
  6. Change one thing at a time. Save, refresh, test, and then make the next change.
Easy rule: Do not paste code and hope. Read the HTML ID, find the matching selector in JavaScript, and test the click. If those match, inspect the console next.

AMDE 355 · Advanced Web Design · Week 10

Interactive Web Experiences.

Move beyond static layouts and design interfaces that respond to people. This week, you’ll explore interaction patterns, microinteractions, feedback, and simple JavaScript behaviors that make a web experience feel clear, useful, and intentional.

Interaction designMicrointeractionsJavaScript basicsFeedback & states

01 / What makes a website interactive?

An interactive web experience responds to a user’s action and communicates what happened. Interaction can be as simple as a navigation link, an expandable panel, a filterable project list, or a form that confirms a successful action. The key is not complexity; it is whether the behavior supports a real user goal.

01

Input

The person does something: clicks, taps, types, selects, scrolls, or uses a keyboard.

02

Response

The interface changes in a way that makes the result visible and understandable.

03

Feedback

The experience confirms the state, provides the next step, or explains how to recover from an error.

Design question: After someone interacts with this element, can they tell what changed and what they can do next?

Example — a microinteraction

Action: A visitor selects “Show project details.”

Response: Additional project information becomes visible.

Feedback: The control label changes to “Hide project details,” and the expanded state is communicated.

02 / Choose an interaction that serves a purpose

Before writing code, define the user need and the behavior. Avoid adding animation or interaction just because it is possible. A useful interaction reduces effort, makes information easier to explore, or provides feedback.

Reveal

Show or hide additional content when someone asks for it, such as an FAQ answer or project description.

Filter

Help people narrow a set of items by category, tag, or another meaningful property.

Confirm

Provide feedback after a meaningful action, such as saving a preference or submitting a form.

Plan the interaction before coding

QuestionExample answer
What is the user trying to do?Read more about a selected project.
What action starts the interaction?Activate the “Show details” button.
What changes?A hidden description becomes visible.
How is the state communicated?The button label changes and the expanded state is announced.
How can the user reverse it?Activate “Hide details.”

03 / Interaction states and microinteractions

Every interactive component needs states that make its behavior understandable. Consider the default state, hover, keyboard focus, active or pressed state, expanded or collapsed state, loading state, success, error, and disabled state where appropriate.

Visual feedback

Changes in color, border, underline, icon, or position help communicate that an element is interactive or has changed state.

Content feedback

Updated labels, expanded text, a result count, or a confirmation message tell the user what happened.

Accessible feedback

Keyboard focus and programmatically communicated state help people using assistive technology understand the interface.

Example — button hover and focus states

interaction-states.css
.action-button {
  background: #b8f34b;
  color: #172033;
  border: 2px solid #172033;
  padding: 0.8rem 1.1rem;
  transition: background-color 160ms ease;
}
.action-button:hover {
  background: #a5df35;
}
.action-button:focus-visible {
  outline: 3px solid #2563eb;
  outline-offset: 4px;
}

Test with a mouse, then use Tab to reach the button. Hover and focus are different states: keyboard users need a visible focus indicator even when there is no pointer.

04 / Build a show-and-hide interaction

This first example uses HTML and CSS to explain the concept. The button is a real button, not a link pretending to be a control. For an actual toggle, JavaScript will update the visible content and accessible state.

HTML structure

details.html
<section class="project-card">
  <h2>Project: Field Notes</h2>
  <p>A short introduction to the project.</p>
  <button
    class="details-toggle"
    type="button"
    aria-expanded="false"
    aria-controls="project-details">
    Show project details
  </button>
  <div id="project-details" hidden>
    <p>Research, sketches, prototypes, and final outcome.</p>
  </div>
</section>

JavaScript behavior

details.js
const toggle = document.querySelector('.details-toggle');
const details = document.querySelector('#project-details');

toggle.addEventListener('click', () => {
  const isExpanded = toggle.getAttribute('aria-expanded') === 'true';

  toggle.setAttribute('aria-expanded', String(!isExpanded));
  details.hidden = isExpanded;
  toggle.textContent = isExpanded
    ? 'Show project details'
    : 'Hide project details';
});

Walk through the logic: find the button and content, listen for a click, check the current state, update the accessible expanded value, show or hide the content, and change the label. In this one-button example, querySelector() is enough because there is only one matching component.

Instructor note: if you place multiple project cards on one page, each button must control its own details panel. Do not reuse the same HTML id on multiple elements. The later instructor section demonstrates a multi-card pattern.

05 / Add a filter to a project list

Filtering helps visitors narrow content without navigating to a new page. The labels should be understandable, and the interface should make it obvious which filter is selected and how many results remain.

HTML example

filter.html
<div class="filters" aria-label="Filter projects">
  <button type="button" data-filter="all">All</button>
  <button type="button" data-filter="web">Web</button>
  <button type="button" data-filter="identity">Identity</button>
</div>

<p id="filter-status" role="status">Showing all projects</p>

<div class="project-grid">
  <article class="project-item" data-category="web">Web project</article>
  <article class="project-item" data-category="identity">Identity project</article>
  <article class="project-item" data-category="web">Another web project</article>
</div>

JavaScript example

filter.js
const filterButtons = document.querySelectorAll('[data-filter]');
const projectItems = document.querySelectorAll('.project-item');
const filterStatus = document.querySelector('#filter-status');

filterButtons.forEach((button) => {
  button.addEventListener('click', () => {
    const category = button.dataset.filter;
    let visibleCount = 0;

    projectItems.forEach((item) => {
      const matches = category === 'all' ||
        item.dataset.category === category;
      item.hidden = !matches;
      if (matches) visibleCount++;
    });

    filterStatus.textContent =
      `Showing ${visibleCount} project(s)`;

    filterButtons.forEach((otherButton) => {
      otherButton.setAttribute(
        'aria-pressed',
        String(otherButton === button)
      );
    });
  });
});

Explain the loop in plain language: when a filter button is selected, the script checks every project, compares its category with the chosen filter, hides items that do not match, and updates the result message. The pressed state communicates the current selection.

06 / Make interaction work for different users

Interactive elements must be operable, perceivable, and understandable. Do not rely on color or hover alone. Make the focus state visible, use native buttons for actions, and communicate changes in content or status.

  • Use a button for an action and an a element for navigation to another location.
  • Ensure interactive controls work with a keyboard.
  • Make focus visible and distinguishable from hover.
  • Use clear labels that describe the action or destination.
  • Provide a visible state change when content expands, filters update, or a task completes.
  • Respect reduced-motion preferences if transitions or animation are used.
  • Test at mobile widths and ensure controls have comfortable touch targets.

Reduced-motion example

reduced-motion.css
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    scroll-behavior: auto;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

Motion should enhance the interaction, not be the only way a user can understand the state or complete the task.

07 / In-class assignment — Create a small interactive portfolio

Build a small interactive section for your Project 1 website or prototype. Choose one useful interaction and implement it in HTML, CSS, and JavaScript. Focus on clarity and behavior rather than visual complexity.

1

Choose one user need

Examples: reveal project details, filter projects by category, or show a clear confirmation after an action.

2

Describe the interaction

Write down the starting state, user action, visible response, and how the user can reverse or recover from it.

3

Build the structure

Use semantic HTML with clear labels and appropriate controls. Keep the content readable before adding styling.

4

Style the states

Add default, hover, and focus styles. Make the active or expanded state clear. Keep motion subtle and purposeful.

5

Add and test the behavior

Use JavaScript to update the interface. Test with a mouse, keyboard, and narrow viewport. Confirm that the state and message match what actually happened.

6

Have a classmate test it

Give them a goal but do not explain how to use the interaction. Record one point of hesitation and make one improvement.

Deliverables

  • A working interactive component or prototype.
  • A short description of the user need and interaction states.
  • HTML, CSS, and JavaScript used for the behavior, where applicable.
  • Evidence of keyboard and responsive testing.
  • A brief reflection describing tester feedback and one revision.
Scope: one well-designed interaction is enough. The goal is to demonstrate that you can plan the behavior, build it, test it, and explain how it improves the experience.

08 / Interaction planning worksheet

Planning questionYour answer
Who is the interaction for?Describe the intended user.
What goal does it support?State the task in one sentence.
What starts the interaction?Click, tap, key press, selection, or input.
What changes?Describe the visible result.
How is the state communicated?Label, status message, expanded state, result count, or focus.
How can the user recover or reverse it?Describe undo, close, reset, or error recovery if relevant.
How will you test it?Write a simple task for a classmate.

09 / Final self-check

  • The interaction solves a real user need rather than adding decoration alone.
  • The action and resulting state are clear.
  • Buttons and links are used for their appropriate purposes.
  • Keyboard focus is visible and the interaction is keyboard operable.
  • State changes are communicated through more than color alone.
  • Responsive behavior has been checked.
  • JavaScript does not show a success state when an action has failed.
  • A classmate tested the interaction without being coached through it.
  • The reflection identifies one observed issue and one improvement.

Instructor-only material · Remove before publishing to students

Instructor Layout Code — Step by Step

Model the design process by building an interaction in stages. First demonstrate the state change with one component, then explain the logic, and finally show how to handle multiple components safely. The examples are teaching samples; test them in a local page or CodePen before presenting.

1

Describe the before and after states

Ask students to sketch the collapsed state and the expanded state. Decide what the button says in each state and how keyboard users will know it changed.

2

Build the semantic HTML

Use a button for the action, a unique ID for the content, and aria-expanded to communicate whether it is open or closed.

3

Read the JavaScript line by line

Identify the elements, attach an event listener, inspect the current state, and update both the content and the accessible state.

4

Scale from one component to many

Use a loop and a relationship between each button and its own panel. Avoid duplicate IDs and avoid assuming the first matching element is always the correct one.

5

Demonstrate a filter

Use data attributes to categorize items, compare each item to the selected filter, update visibility, and announce the number of matching results.

6

Test and debug

Test the default state, repeated activation, keyboard operation, no-match states, and mobile layout. Ask students to predict and then explain any unexpected result.

Teaching note: keep the distinction clear between a visual prototype and a working interaction. If the interface looks clickable but does nothing, it is not a completed interactive behavior. Likewise, do not display confirmation text unless the action has actually succeeded.

Instructor example A — One disclosure component

Use this example to introduce DOM selection, event listeners, attributes, and the hidden property. Type it in small sections and test after each step.

disclosure.html
<button id="details-button"
  type="button"
  aria-expanded="false"
  aria-controls="details-panel">
  Show details
</button>

<div id="details-panel" hidden>
  <p>Additional information appears here.</p>
</div>
disclosure.js
const button = document.querySelector('#details-button');
const panel = document.querySelector('#details-panel');

button.addEventListener('click', () => {
  const expanded = button.getAttribute('aria-expanded') === 'true';
  button.setAttribute('aria-expanded', String(!expanded));
  panel.hidden = expanded;
  button.textContent = expanded ? 'Show details' : 'Hide details';
});

Explain each line: querySelector() finds the element, addEventListener() waits for a click, getAttribute() reads the current state, and the final statements update the state, panel, and label together.

Instructor example B — Multiple independent disclosure cards

When a page has multiple cards, each button must control its own panel. This pattern avoids duplicate IDs by using a button inside each component and finding the related panel within that component.

multiple-disclosures.html
<article class="project-card">
  <h2>Project A</h2>
  <button class="details-toggle"
    type="button"
    aria-expanded="false">Show details</button>
  <div class="details-panel" hidden>
    <p>Details for Project A.</p>
  </div>
</article>

<article class="project-card">
  <h2>Project B</h2>
  <button class="details-toggle"
    type="button"
    aria-expanded="false">Show details</button>
  <div class="details-panel" hidden>
    <p>Details for Project B.</p>
  </div>
</article>
multiple-disclosures.js
document.querySelectorAll('.project-card').forEach((card) => {
  const button = card.querySelector('.details-toggle');
  const panel = card.querySelector('.details-panel');

  button.addEventListener('click', () => {
    const expanded = button.getAttribute('aria-expanded') === 'true';
    button.setAttribute('aria-expanded', String(!expanded));
    panel.hidden = expanded;
    button.textContent = expanded ? 'Show details' : 'Hide details';
  });
});

Explain that card.querySelector() searches within the current card. Each click handler therefore updates the correct button and panel rather than the first panel on the page.

Instructor example C — A simple category filter

Have students identify the data relationship: a filter button has a category, and each project item has a category. The script compares those values to decide which items to show.

filter-demo.js
const buttons = document.querySelectorAll('[data-filter]');
const items = document.querySelectorAll('[data-category]');

buttons.forEach((button) => {
  button.addEventListener('click', () => {
    const selected = button.dataset.filter;

    items.forEach((item) => {
      const showAll = selected === 'all';
      const matches = item.dataset.category === selected;
      item.hidden = !(showAll || matches);
    });
  });
});

Test all categories and the All button. Ask what should happen if there are zero matching items. A more complete version should display a helpful no-results message and communicate which filter is selected.

Instructor example D — Make the no-results state explicit

A filter can technically work while still leaving the visitor unsure whether the page is empty, broken, or loading. Use a status message to explain the result.

empty-state.html
<p id="results-status" role="status">
  Showing all projects.
</p>
<p id="no-results" hidden>
  No projects match this filter. Choose another category.
</p>

Explain that the status text should be updated by the filtering logic based on the actual number of visible items. Do not show “No projects match” when results are present.

Instructor example E — A quick debugging sequence

  1. Check the selector. Does the class or ID in JavaScript match the HTML exactly?
  2. Check the console. Is there an error that stops the script from running?
  3. Check timing. Does the script run after the relevant HTML elements exist?
  4. Check the event. Is the listener attached to the element the user actually activates?
  5. Check state synchronization. Do the visible content, button label, and accessible state all agree?
  6. Test repeat use. Can the user activate and reverse the behavior more than once?

Common mistake: the button label changes to “Hide details,” but the panel stays hidden.

Debugging question: Is the script selecting the correct panel? Is another CSS rule overriding the display? Is the hidden property being set from the current state correctly?

Instructor facilitation checklist

  • Ask students to describe the interaction in plain language before coding.
  • Demonstrate the default state before showing JavaScript.
  • Explain each selector and event listener in ordinary language.
  • Keep the visual design simple enough that behavior remains the focus.
  • Test keyboard operation, repeat activation, and responsive behavior.
  • Ask students to explain what happens when something goes wrong.
  • Require a short user test and one revision based on observation.