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:
// 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
| Code | Plain-English meaning |
|---|---|
const button = ... | Give the thing we found a short name so we can use it again. |
document | The 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. |
textContent | The 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.
<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.
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.
<p id="status-message">
This is the original message.
</p>
<button id="change-message">
Change message
</button>
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.
<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>
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:
- Find the button and the content panel.
- When the button is clicked, check whether the panel is currently hidden.
- If it is hidden, show it. If it is visible, hide it.
- Update the button label so it describes the next action.
- Update
aria-expandedso 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
- Check the spelling. Does
id="details-button"matchquerySelector("#details-button")exactly? - Check the symbol. A class selector starts with a dot, like
".project-card". An ID selector starts with a hash, like"#details-button". - Check where the script lives. Put the script just before
</body>or usedeferif the script is in the document head. - Check the browser console. Right-click the page, choose Inspect, and open Console. Red error messages often point to the line with the problem.
- 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. - Change one thing at a time. Save, refresh, test, and then make the next change.
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.
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.
Input
The person does something: clicks, taps, types, selects, scrolls, or uses a keyboard.
Response
The interface changes in a way that makes the result visible and understandable.
Feedback
The experience confirms the state, provides the next step, or explains how to recover from an error.
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
| Question | Example 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
.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
<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
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.
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
<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
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
buttonfor an action and anaelement 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
@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.
Choose one user need
Examples: reveal project details, filter projects by category, or show a clear confirmation after an action.
Describe the interaction
Write down the starting state, user action, visible response, and how the user can reverse or recover from it.
Build the structure
Use semantic HTML with clear labels and appropriate controls. Keep the content readable before adding styling.
Style the states
Add default, hover, and focus styles. Make the active or expanded state clear. Keep motion subtle and purposeful.
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.
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.
08 / Interaction planning worksheet
| Planning question | Your 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.
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.
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.
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.
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.
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.
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.
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.
<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>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.
<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>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.
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.
<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
- Check the selector. Does the class or ID in JavaScript match the HTML exactly?
- Check the console. Is there an error that stops the script from running?
- Check timing. Does the script run after the relevant HTML elements exist?
- Check the event. Is the listener attached to the element the user actually activates?
- Check state synchronization. Do the visible content, button label, and accessible state all agree?
- 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.