How to Create Dynamic Web Pages with HTML and CSS
Learn how HTML, CSS, and JavaScript work together to build a responsive page with a real interaction, considerate motion, and a clear path to publishing.
A dynamic web page uses HTML for structure and content, CSS for presentation and layout, and usually JavaScript when it needs to respond to input or data. CSS can make visual changes feel dynamic with transitions and animations, but it does not replace JavaScript for button behavior, form validation, or changing content in response to an event. MDN’s beginner guide follows the same practical progression: plan, build, style, add behavior, and publish.
1. What “dynamic” means
The word can describe two different things:
- Visual motion or state styling: CSS can change how an element looks when it is hovered, focused, or assigned a different class. Transitions smooth a change between styles; keyframe animations describe a sequence.
- Interactive or data-driven behavior: JavaScript handles events, makes decisions, validates data, and changes page content or state.
A browser parses HTML into a document structure, applies CSS to present it, and executes JavaScript that can respond to events or modify the document. Start with the simplest tool that matches the task. See MDN’s guides to JavaScript interactivity, CSS animations, and CSS transitions.
| What you want | Start with |
|---|---|
| Organize headings, text, lists, forms, and controls | HTML |
| Set colors, spacing, typography, and responsive layout | CSS |
| Smoothly change between two visual states | CSS transition |
| Run a decorative sequence or repeating motion | CSS keyframes |
| React to a click, validate input, or update content from data | JavaScript, with HTML for structure and CSS for presentation |
| Create or control animation timing in code | Web Animations API |
| Animate a transition between views or page documents | View Transition API, after checking browser support |
2. Build a small working page
This example creates a responsive task list. HTML defines the content, CSS handles layout and motion, and JavaScript adds items and marks them complete. It runs as a single local file with no packages or build tools.
Step 1: Create index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="A small interactive task list.">
<title>A Small Dynamic Task List</title>
<style>
:root {
color-scheme: light;
font-family: system-ui, sans-serif;
line-height: 1.5;
background: #f4f7fb;
color: #172033;
}
* { box-sizing: border-box; }
body { margin: 0; padding: 2rem 1rem; }
main {
width: min(100%, 38rem);
margin: 0 auto;
padding: clamp(1.25rem, 4vw, 2rem);
background: white;
border: 1px solid #d9e0eb;
border-radius: 1rem;
box-shadow: 0 1rem 3rem rgb(23 32 51 / 8%);
}
h1 { line-height: 1.15; }
form { display: flex; gap: .6rem; margin: 1.5rem 0; }
input, button { font: inherit; }
input {
min-width: 0;
flex: 1;
padding: .7rem .8rem;
border: 1px solid #9aa8bd;
border-radius: .5rem;
}
button {
padding: .65rem .9rem;
border: 0;
border-radius: .5rem;
background: #2457d6;
color: white;
cursor: pointer;
}
button:focus-visible, input:focus-visible, li:focus-visible {
outline: 3px solid #ffbf47;
outline-offset: 3px;
}
ul { display: grid; gap: .6rem; padding: 0; list-style: none; }
li {
display: flex;
align-items: center;
gap: .7rem;
padding: .7rem;
border: 1px solid #e0e5ed;
border-radius: .6rem;
animation: appear .18s ease-out;
}
li span { flex: 1; overflow-wrap: anywhere; }
li.done span { color: #637086; text-decoration: line-through; }
.remove { background: #eef1f6; color: #172033; }
#status { min-height: 1.5em; color: #40516b; }
@keyframes appear {
from { opacity: 0; transform: translateY(.35rem); }
to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
scroll-behavior: auto !important;
animation-duration: .01ms !important;
animation-iteration-count: 1 !important;
transition-duration: .01ms !important;
}
}
@media (max-width: 25rem) {
form { flex-direction: column; }
}
</style>
</head>
<body>
<main>
<h1>My tasks</h1>
<p>Add a task, then mark it complete when you finish.</p>
<form id="task-form">
<label for="task-input">New task</label>
<input id="task-input" name="task" maxlength="120" required>
<button type="submit">Add task</button>
</form>
<p id="status" role="status" aria-live="polite"></p>
<ul id="task-list" aria-label="Tasks"></ul>
</main>
<script>
const form = document.querySelector('#task-form');
const input = document.querySelector('#task-input');
const list = document.querySelector('#task-list');
const status = document.querySelector('#status');
form.addEventListener('submit', (event) => {
event.preventDefault();
const title = input.value.trim();
if (!title) {
input.focus();
status.textContent = 'Enter a task before adding it.';
return;
}
const item = document.createElement('li');
const complete = document.createElement('input');
complete.type = 'checkbox';
complete.setAttribute('aria-label', `Mark ${title} complete`);
complete.addEventListener('change', () => {
item.classList.toggle('done', complete.checked);
});
const text = document.createElement('span');
text.textContent = title;
const remove = document.createElement('button');
remove.type = 'button';
remove.className = 'remove';
remove.textContent = 'Remove';
remove.setAttribute('aria-label', `Remove ${title}`);
remove.addEventListener('click', () => item.remove());
item.append(complete, text, remove);
list.append(item);
form.reset();
input.focus();
status.textContent = `Added: ${title}`;
});
</script>
</body>
</html>
Step 2: Open and change it
- Save the code as
index.html. - Open the file in a current browser.
- Enter text and submit the form. The submit event adds a list item without reloading the page.
- Use the checkbox to toggle a CSS class, which changes the item’s appearance. Use Remove to delete it.
- Resize the window to see the layout adapt. The reduced-motion media query minimizes the entrance animation for visitors who request less motion.
The example uses textContent for user-provided text so the browser treats it as text rather than parsing it as HTML. That is a safer default than inserting untrusted input with innerHTML. The list exists only in memory: refreshing the page clears it. To keep tasks, add storage or a server-side data source and decide how errors and concurrent edits should work.
3. Separate files as the page grows
For a larger page, move the CSS and JavaScript into separate files. This makes each responsibility easier to find and lets browsers cache static assets.
<!-- In index.html, inside head -->
<link rel="stylesheet" href="styles.css">
<!-- Before the closing body tag -->
<script src="app.js" defer></script>
Put the rules from the example’s style block in styles.css, and the event-listener code in app.js. The defer attribute lets the browser finish parsing the document before running the external script, so the elements can be selected when the code runs.
4. Make the page useful and robust
Structure and accessibility
- Use semantic elements such as
main, headings, lists, forms, labels, and buttons for their intended purpose. - Give every form control a visible label. Use native controls when they already provide the behavior you need.
- Make interactive elements keyboard-operable and show a visible focus state.
- Announce important asynchronous status changes where appropriate, as the example does with a polite live region.
- Do not convey a state only through color; pair color with text, an icon, or another cue.
Responsive layout
Start with a narrow viewport, use flexible widths and spacing, and add breakpoints only when the content needs them. The example uses a width cap, flexible form fields, wrapping text, and a small-screen breakpoint. Test long labels and user content, not only short sample text.
Choose CSS motion carefully
Use a transition when an existing style changes between states, such as a button color on hover. Use keyframes for a defined sequence such as the new-item entrance above. Not every CSS property can be animated, and transitions involving auto may behave differently across browsers. Avoid making essential information depend on animation. Respect reduced-motion preferences; for motion that runs for a while or repeats, consider a way for users to pause or disable it. See MDN’s animation accessibility guidance.
When state becomes more complex
Keep state changes explicit. For a small interface, a class toggle may be enough. If content comes from a server, show loading, success, empty, and error states; validate data at the appropriate boundary; and avoid assuming a request always succeeds. For animation whose timing or playback must be controlled by JavaScript, consider the Web Animations API. For transitions between page views, check current compatibility for the View Transition API before depending on it.
5. Preview, debug, and publish
- Preview locally. Open the HTML file for a simple static example. If the page uses modules, fetch requests, or browser features that require a web origin, use a local development server instead of opening it as a file.
- Check the console. In browser developer tools, look for syntax errors, missing files, and failed network requests. Fix the first relevant error and reload.
- Test the actual tasks. Try keyboard-only navigation, empty and long input, narrow and wide screens, and reduced-motion settings.
- Publish the finished files. Put the HTML, CSS, JavaScript, and any assets on a web host or static-site host, then open the published URL and check that asset paths and interactions work there too. MDN’s publishing guide covers the beginner workflow.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Clicking the button reloads the page | The form’s default submit behavior is still active | Handle the form’s submit event and call preventDefault() when handling it in JavaScript. |
| Nothing happens when a control is clicked | A script error, incorrect selector, or script running before the elements exist | Read the first console error, verify IDs and selectors, and use a deferred external script or place the script after the markup. |
| Styles do not appear | Incorrect stylesheet path, filename mismatch, or invalid CSS | Check the browser Network panel and console; confirm the path is relative to the HTML file and inspect nearby CSS syntax. |
| Added text behaves like markup | Untrusted input was inserted as HTML | Use textContent for plain text. Only parse HTML that has been deliberately sanitized for the context. |
| A transition does not run | The property is not animatable, the starting value is missing, or a rule is overridden | Inspect computed styles, define both endpoint values, and choose a property that can transition. Do not assume values involving auto interpolate consistently. |
| The page is awkward on a phone | Fixed widths, unbreakable content, or controls that do not fit | Use a viewport meta tag, flexible sizing, wrapping, and a breakpoint based on where the content stops fitting. |
| New tasks disappear after refresh | The example stores them only in the current document’s memory | Add persistent storage or a backend, and handle unavailable storage, failed requests, and conflicting updates. |
| Motion causes discomfort | Animation is continuous, intense, or ignores motion preferences | Honor prefers-reduced-motion; for longer or repeating motion, provide a pause or disable control. |
7. Performance, reliability, and cost
A small HTML/CSS/JavaScript page has no required service cost by itself. Hosting, a domain, data storage, and third-party services may have separate costs; choose them based on the site’s requirements. Keep the initial page small, avoid unnecessary scripts, and size images appropriately. Browser caching can help with separate static assets. If the page depends on remote data, account for slow or failed requests and provide an understandable fallback. A visual animation is not a substitute for confirming that the underlying action succeeded.
8. Inspect the finished page with a screenshot
A screenshot is useful for checking layout at a particular viewport and catching issues such as clipped content, unexpected spacing, or a consent overlay covering the page. It does not replace keyboard, interaction, or accessibility checks. For a manual check, open the published page at desktop and mobile widths and compare the visible states you care about.
Or skip the browser setup
For an API capture, use ScreenshotNeo. It returns a screenshot or PDF from one GET request. The API accepts options for viewport and device, full-page capture, CSS selector capture, dark mode, custom CSS or JavaScript, waits, headers, cookies, caching, and more. See the ScreenshotNeo API documentation for parameters and response details.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res); // In Node.js, write the response bytes with node:fs/promises.
Cookie banners are accepted before capture and more than 60 known consent platforms, newsletter popups, and chat widgets are removed; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. An MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month with no card.
9. Frequently asked questions
Can I create a dynamic page with only HTML and CSS?
You can create responsive layouts, hover and focus states, and CSS animations with HTML and CSS. For behavior driven by clicks, form input, or computed data, add JavaScript.
Do I need a framework?
No. The example works in a browser without a framework. Consider a framework when the application’s size, shared components, or state management needs justify the added tools.
Will the example remember data?
No. Its task list lives only in the open page. Persistence requires browser storage or a server-side data source.
How do I make motion accessible?
Keep essential information available without motion, honor reduced-motion preferences, and offer a pause or disable control for motion that runs for a while or repeats.


