#2 HTML+ CSS+JSS using Code Pen

 

🌐 HTML, CSS & JavaScript — 10‑Module CodePen Course


Please visit this page before Starting this Hands on : https://www.freecodecamp.org/news/how-to-use-codepen/

A hands-on, progressive course you can complete entirely in CodePen. Each module includes: goals, concepts, a minimal HTML/CSS/JS starter you can paste into the three CodePen panels, and practice tasks. Difficulty increases step by step.


🧰 Before You Start: CodePen Tips

  • Open a new Pen. Use the three panels: HTML, CSS, JS.

  • CodePen auto-injects <html>, <head>, and <body> for you. You can paste full HTML documents, but simpler is to paste only what goes inside <body>.

  • Open the Console (bottom right ▸ ▸ Console) to see console.log() output.

  • If you need fonts/icons later: Settings → HTML → Add external resources (e.g., Google Fonts, Font Awesome).


Module 1 — HTML Foundations

Goal: Understand core tags, semantic structure, links, images, lists, and basic accessibility.

Key concepts: Elements, attributes, headings, paragraphs, lists, links (<a>), images (<img>), semantic tags (<header> <nav> <main> <section> <article> <footer>), alt text.

HTML (paste in HTML panel):

<header>
  <h1>My First Site</h1>
  <nav>
    <a href="#home">Home</a> · <a href="#about">About</a> · <a href="#contact">Contact</a>
  </nav>
</header>
<main>
  <section id="home">
    <h2>Welcome πŸ‘‹</h2>
    <p>This page shows basic <strong>HTML</strong> building blocks.</p>
    <img src="https://images.unsplash.com/photo-1526378722484-bd91ca387e72?w=800" alt="Laptop on desk" width="320">
  </section>
  <section id="about">
    <h2>About</h2>
    <p>HTML provides <em>structure</em> to content. Here is a list:</p>
    <ul>
      <li>Headings and paragraphs</li>
      <li>Lists</li>
      <li>Links and images</li>
    </ul>
  </section>
  <section id="contact">
    <h2>Contact</h2>
    <p>Email: <a href="mailto:hello@example.com">hello@example.com</a></p>
  </section>
</main>
<footer>
  <small>© 2025 My First Site</small>
</footer>

CSS: (optional for now; add in Module 2)

JS: (none yet)

Practice: Add a new section with an ordered list of your learning goals. Add proper alt text to any new images.


Module 2 — CSS Basics & The Box Model

Goal: Style text and layout basics; understand padding, border, margin, display.

Key concepts: Selectors, properties, cascade & specificity, margin/padding/border, display:block/inline/inline-block.

HTML:

<header class="site-header">
  <h1>Stylish Site</h1>
  <p class="tagline">Learning CSS one step at a time.</p>
</header>
<main class="content">
  <article class="card">
    <h2>Card Title</h2>
    <p>Cards are boxes with padding, border, and margin.</p>
    <a class="btn" href="#">Read more</a>
  </article>
  <article class="card">
    <h2>Another Card</h2>
    <p>Notice consistent spacing and typography.</p>
    <a class="btn" href="#">Details</a>
  </article>
</main>

CSS (paste in CSS panel):

:root { --bg: #f7fafc; --ink: #1a202c; --brand: #2563eb; }
* { box-sizing: border-box; }
body { font-family: system-ui, Arial, sans-serif; background: var(--bg); color: var(--ink); line-height: 1.6; margin: 0; }
.site-header { text-align: center; padding: 2rem 1rem; }
.tagline { color: #4a5568; margin-top: .25rem; }
.content { display: flex; gap: 1rem; padding: 1rem; max-width: 960px; margin: 0 auto; flex-wrap: wrap; }
.card { background: white; border: 1px solid #e2e8f0; border-radius: 12px; padding: 1rem; width: 300px; }
.card h2 { margin-top: 0; }
.btn { display: inline-block; background: var(--brand); color: white; padding: .5rem .75rem; border-radius: 8px; text-decoration: none; }
.btn:hover { filter: brightness(.95); }

JS: (none yet)

Practice: Add a third card. Change the --brand color. Add :hover style to .card.


Module 3 — JavaScript Fundamentals

Goal: Write variables, functions, and handle click events.

Key concepts: const/let, functions, events, DOM selection, console.log.

HTML:

<h1>JS Basics Playground</h1>
<button id="helloBtn">Say Hello</button>
<p id="output"></p>

CSS:

body { font-family: system-ui; padding: 1.5rem; }
#output { margin-top: 1rem; font-weight: 600; }
button { padding: .6rem 1rem; border: 0; border-radius: 8px; cursor: pointer; }

JS (paste in JS panel):

const btn = document.getElementById('helloBtn');
const out = document.getElementById('output');

function greet(name = 'friend') { return `Hello, ${name}!`; }

btn.addEventListener('click', () => {
  const name = prompt('Your name?');
  out.textContent = greet(name || undefined);
  console.log('Greeted:', name);
});

Practice: Add another button that clears the message. Log the current time each click.


Module 4 — DOM Manipulation & Event Delegation

Goal: Create, insert, and remove elements dynamically.

Key concepts: document.createElement, append, remove, event delegation (.addEventListener on a parent), dataset attributes.

HTML:

<h1>Mini To‑Do</h1>
<form id="todoForm">
  <input id="todoInput" placeholder="Add a task" required>
  <button>Add</button>
</form>
<ul id="list"></ul>

CSS:

body { font-family: system-ui; max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
li { display: flex; justify-content: space-between; padding: .5rem .75rem; border: 1px solid #e2e8f0; border-radius: 8px; margin-top: .5rem; }
button { cursor: pointer; }

JS:

const form = document.getElementById('todoForm');
const input = document.getElementById('todoInput');
const list = document.getElementById('list');

form.addEventListener('submit', e => {
  e.preventDefault();
  const text = input.value.trim();
  if (!text) return;
  const li = document.createElement('li');
  li.innerHTML = `<span>${text}</span> <button data-action="remove">✖</button>`;
  list.append(li);
  input.value = '';
});

list.addEventListener('click', e => {
  if (e.target.matches('button[data-action="remove"]')) {
    e.target.closest('li').remove();
  }
});

Practice: Add a “complete” toggle that strikes through text. Persist tasks to localStorage (hint in Module 9).


Module 5 — Responsive Layouts: Flexbox & Grid

Goal: Build responsive layouts that adapt to screen size.

Key concepts: Flexbox (axis, gap, wrap, alignment), CSS Grid (columns, auto-fit, minmax), media queries.

HTML:

<header class="topbar"><h1>Photo Grid</h1></header>
<main class="gallery">
  <img src="https://picsum.photos/seed/1/600/400" alt="Random 1">
  <img src="https://picsum.photos/seed/2/600/400" alt="Random 2">
  <img src="https://picsum.photos/seed/3/600/400" alt="Random 3">
  <img src="https://picsum.photos/seed/4/600/400" alt="Random 4">
  <img src="https://picsum.photos/seed/5/600/400" alt="Random 5">
  <img src="https://picsum.photos/seed/6/600/400" alt="Random 6">
</main>
<footer class="foot">© 2025</footer>

CSS:

body { margin: 0; font-family: system-ui; }
.topbar, .foot { padding: 1rem; text-align: center; background: #111; color: #fff; }
.gallery { display: grid; gap: 10px; padding: 10px; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.gallery img { width: 100%; height: 200px; object-fit: cover; border-radius: 12px; }

JS: (none required)

Practice: Add a responsive card row using Flexbox below the grid. At <600px, make images taller with a media query.


Module 6 — Forms & Client‑Side Validation

Goal: Create accessible forms and validate inputs.

Key concepts: Labels, required, HTML5 input types, constraint API, custom messages.

HTML:

<h1>Sign Up</h1>
<form id="signup" novalidate>
  <label>Email
    <input type="email" id="email" required placeholder="you@example.com">
  </label>
  <label>Password
    <input type="password" id="pass" required minlength="6" placeholder="At least 6 chars">
  </label>
  <label>Confirm Password
    <input type="password" id="confirm" required>
  </label>
  <button>Create Account</button>
  <p id="errors" role="alert" aria-live="polite"></p>
</form>

CSS:

form { display: grid; gap: .75rem; max-width: 360px; margin: 2rem auto; font-family: system-ui; }
input { width: 100%; padding: .6rem; border: 1px solid #cbd5e1; border-radius: 8px; }
#errors { color: #b91c1c; font-weight: 600; }
button { padding: .6rem 1rem; border: 0; border-radius: 8px; cursor: pointer; }

JS:

const form = document.getElementById('signup');
const email = document.getElementById('email');
const pass = document.getElementById('pass');
const confirmPass = document.getElementById('confirm');
const errors = document.getElementById('errors');

form.addEventListener('submit', e => {
  errors.textContent = '';
  if (!email.checkValidity()) {
    e.preventDefault();
    errors.textContent = 'Please enter a valid email.';
    return;
  }
  if (pass.value.length < 6) {
    e.preventDefault();
    errors.textContent = 'Password must be at least 6 characters.';
    return;
  }
  if (pass.value !== confirmPass.value) {
    e.preventDefault();
    errors.textContent = 'Passwords do not match.';
  }
});

Practice: Add real-time validation (on input) that clears errors as the user types.


Module 7 — Reusable Components, BEM & Theming

Goal: Create reusable UI (cards, buttons, tabs), organize CSS with BEM, and add light/dark theme via CSS variables.

Key concepts: BEM naming (block__element--modifier), CSS variables, toggling a theme class, tabs component.

HTML:

<header class="site">
  <h1>Components & Themes</h1>
  <button id="themeToggle" class="btn btn--ghost">Toggle Theme</button>
</header>

<section class="tabs" data-tabs>
  <nav class="tabs__list">
    <button class="tabs__tab tabs__tab--active" data-tab="one">One</button>
    <button class="tabs__tab" data-tab="two">Two</button>
    <button class="tabs__tab" data-tab="three">Three</button>
  </nav>
  <div class="tabs__panel tabs__panel--active" data-panel="one">Panel One Content</div>
  <div class="tabs__panel" data-panel="two">Panel Two Content</div>
  <div class="tabs__panel" data-panel="three">Panel Three Content</div>
</section>

CSS:

:root { --bg:#ffffff; --ink:#111827; --card:#ffffff; --border:#e5e7eb; }
.dark { --bg:#0b1220; --ink:#e5e7eb; --card:#111827; --border:#374151; }
body { background: var(--bg); color: var(--ink); font-family: system-ui; margin: 0; }
.site { display: flex; justify-content: space-between; align-items: center; padding: 1rem; border-bottom: 1px solid var(--border); }
.btn { padding:.5rem .75rem; border-radius: 10px; border:1px solid var(--border); background: var(--card); cursor: pointer; }
.btn--ghost { background: transparent; }
.tabs { max-width: 720px; margin: 1rem auto; padding: 1rem; }
.tabs__list { display: flex; gap:.5rem; margin-bottom: .75rem; }
.tabs__tab { border:1px solid var(--border); background: var(--card); padding:.5rem .75rem; border-radius: 8px; cursor: pointer; }
.tabs__tab--active { outline: 2px solid #60a5fa; }
.tabs__panel { display: none; border:1px solid var(--border); padding: 1rem; border-radius: 12px; }
.tabs__panel--active { display: block; }

JS:

const root = document.documentElement;
const themeBtn = document.getElementById('themeToggle');

themeBtn.addEventListener('click', () => {
  document.body.classList.toggle('dark');
});

const tabs = document.querySelector('[data-tabs]');
const tabButtons = tabs.querySelectorAll('.tabs__tab');
const panels = tabs.querySelectorAll('.tabs__panel');

tabs.addEventListener('click', (e) => {
  const btn = e.target.closest('.tabs__tab');
  if (!btn) return;
  const target = btn.dataset.tab;

  tabButtons.forEach(b => b.classList.toggle('tabs__tab--active', b === btn));
  panels.forEach(p => p.classList.toggle('tabs__panel--active', p.dataset.panel === target));
});

Practice: Add a .btn--primary modifier and use CSS variables to change its background in dark mode.


Module 8 — Fetch API, JSON & Loading States

Goal: Request data from an API and render it.

Key concepts: fetch, async/await, error handling, skeleton/loading UI.

HTML:

<h1>Posts (from JSONPlaceholder)</h1>
<div id="status">Loading…</div>
<section id="posts" class="posts"></section>

CSS:

body { font-family: system-ui; max-width: 900px; margin: 2rem auto; padding: 0 1rem; }
.posts { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }
.post { border:1px solid #e5e7eb; border-radius: 12px; padding: 1rem; background: #fff; }

JS:

const status = document.getElementById('status');
const postsEl = document.getElementById('posts');

(async function load() {
  try {
    const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=8');
    if (!res.ok) throw new Error('Network error');
    const posts = await res.json();
    status.textContent = '';
    postsEl.innerHTML = posts.map(p => `
      <article class="post">
        <h3>${p.title}</h3>
        <p>${p.body}</p>
      </article>
    `).join('');
  } catch (err) {
    status.textContent = 'Failed to load posts.';
    console.error(err);
  }
})();

Practice: Add a search input that filters posts by title (client-side). Show a retry button on error.


Module 9 — State, localStorage & Simple Hash Routing

Goal: Persist data locally and build a tiny SPA with #routes.

Key concepts: localStorage, serialization, reactive rendering, window.onhashchange.

HTML:

<header class="spa">
  <a href="#/home">Home</a> · <a href="#/favorites">Favorites</a> · <a href="#/about">About</a>
</header>
<main id="view"></main>

CSS:

body { font-family: system-ui; }
.spa { display:flex; gap:1rem; padding:1rem; border-bottom:1px solid #e5e7eb; }
.card { border:1px solid #e5e7eb; border-radius: 10px; padding: .75rem; margin:.5rem 0; }
.btn { padding:.4rem .7rem; border:0; border-radius:8px; cursor:pointer; }

JS:

const view = document.getElementById('view');
const KEY = 'favorites:v1';
let favorites = JSON.parse(localStorage.getItem(KEY) || '[]');

function save() { localStorage.setItem(KEY, JSON.stringify(favorites)); }
function toggleFav(id) {
  favorites = favorites.includes(id) ? favorites.filter(x => x !== id) : [...favorites, id];
  save();
  render();
}

const pages = {
  '/home': () => {
    const items = Array.from({length: 5}, (_,i) => ({ id: i+1, title: `Item ${i+1}` }));
    return `
      <h1>Home</h1>
      ${items.map(it => `
        <div class="card">
          <strong>${it.title}</strong>
          <button class="btn" data-id="${it.id}">
            ${favorites.includes(it.id) ? '★ Remove' : '☆ Fav'}
          </button>
        </div>`).join('')}
    `;
  },
  '/favorites': () => `
    <h1>Favorites</h1>
    <pre>${JSON.stringify(favorites, null, 2)}</pre>
  `,
  '/about': () => `<h1>About</h1><p>Simple hash router demo.</p>`
};

function render() {
  const route = location.hash.replace('#', '') || '/home';
  const page = pages[route] || (() => '<h1>404</h1>');
  view.innerHTML = page();
}

window.addEventListener('hashchange', render);
view.addEventListener('click', (e) => {
  const btn = e.target.closest('button[data-id]');
  if (btn) toggleFav(Number(btn.dataset.id));
});

render();

Practice: Add a /settings route to clear favorites. Persist a theme choice (reuse Module 7 pattern).


πŸ† Module 10 — Notes App Code

πŸ“„ HTML (structure)

<div class="app">
  <h1>πŸ“ My Notes</h1>

  <!-- Input Section -->
  <div class="note-input">
    <textarea id="noteText" placeholder="Write a note..."></textarea>
    <button id="addNoteBtn">Add Note</button>
  </div>

  <!-- Search -->
  <input type="text" id="searchBox" placeholder="Search notes...">

  <!-- Notes List -->
  <div id="notesContainer" class="notes"></div>
</div>

🎨 CSS (style & layout)

body {
  font-family: Arial, sans-serif;
  background: #f4f7fb;
  margin: 0;
  padding: 20px;
  display: flex;
  justify-content: center;
}

.app {
  max-width: 700px;
  width: 100%;
  background: #fff;
  padding: 20px;
  border-radius: 12px;
  box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}

h1 {
  text-align: center;
}

.note-input {
  display: flex;
  gap: 10px;
  margin-bottom: 15px;
}

.note-input textarea {
  flex: 1;
  padding: 10px;
  border-radius: 8px;
  border: 1px solid #ddd;
  resize: none;
  height: 60px;
}

.note-input button {
  background: #007bff;
  border: none;
  color: white;
  padding: 12px 18px;
  border-radius: 8px;
  cursor: pointer;
}

.note-input button:hover {
  background: #0056b3;
}

#searchBox {
  width: 100%;
  padding: 10px;
  margin-bottom: 15px;
  border-radius: 8px;
  border: 1px solid #ddd;
}

.notes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 15px;
}

.note {
  background: #fdfdfd;
  padding: 15px;
  border-radius: 10px;
  box-shadow: 0 2px 6px rgba(0,0,0,0.1);
  position: relative;
}

.note p {
  margin: 0;
  white-space: pre-wrap;
}

.note .actions {
  margin-top: 10px;
  display: flex;
  justify-content: space-between;
}

.note button {
  border: none;
  background: #eee;
  padding: 5px 10px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 12px;
}

.note button:hover {
  background: #ddd;
}

⚡ JavaScript (logic)

const noteText = document.getElementById("noteText");
const addNoteBtn = document.getElementById("addNoteBtn");
const notesContainer = document.getElementById("notesContainer");
const searchBox = document.getElementById("searchBox");

let notes = JSON.parse(localStorage.getItem("notes")) || [];

// Save notes to localStorage
function saveNotes() {
  localStorage.setItem("notes", JSON.stringify(notes));
}

// Render notes
function renderNotes(filter = "") {
  notesContainer.innerHTML = "";
  notes
    .filter(note => note.text.toLowerCase().includes(filter.toLowerCase()))
    .forEach((note, index) => {
      const noteEl = document.createElement("div");
      noteEl.classList.add("note");

      const textEl = document.createElement("p");
      textEl.textContent = note.text;

      const actionsEl = document.createElement("div");
      actionsEl.classList.add("actions");

      const editBtn = document.createElement("button");
      editBtn.textContent = "Edit";
      editBtn.onclick = () => {
        const newText = prompt("Edit note:", note.text);
        if (newText !== null) {
          notes[index].text = newText.trim();
          saveNotes();
          renderNotes(searchBox.value);
        }
      };

      const deleteBtn = document.createElement("button");
      deleteBtn.textContent = "Delete";
      deleteBtn.onclick = () => {
        notes.splice(index, 1);
        saveNotes();
        renderNotes(searchBox.value);
      };

      actionsEl.appendChild(editBtn);
      actionsEl.appendChild(deleteBtn);

      noteEl.appendChild(textEl);
      noteEl.appendChild(actionsEl);

      notesContainer.appendChild(noteEl);
    });
}

// Add note
addNoteBtn.addEventListener("click", () => {
  const text = noteText.value.trim();
  if (text) {
    notes.push({ text });
    saveNotes();
    renderNotes(searchBox.value);
    noteText.value = "";
  }
});

// Search notes
searchBox.addEventListener("input", (e) => {
  renderNotes(e.target.value);
});

// Initial render
renderNotes();

✅ How it works:

  1. Type a note and click Add Note → it shows in the grid.

  2. Use Edit/Delete buttons for each note.

  3. Notes are saved in localStorage → they stay even after page refresh.

  4. Search box filters notes instantly.


πŸ‘‰

Comments