// intern guideline

HTML, CSS & JS
— sat rigtigt op fra dag ét.

Denne side er selv bygget efter reglen den underviser i: HTML alene i index.html, CSS alene i assets/styles/, JS alene i assets/js/. Ingen inline style, ingen inline onclick — nul undtagelser.

localhost:5500/Guideline/
# mappestruktur for projektet
Guideline/
├── index.html
└── assets/
    ├── styles/
    │   └── style.css
    └── js/
        └── script.js
HTML

HyperText Markup Language

HTML er skelettet på enhver hjemmeside. Det er ikke et programmeringssprog — det er et markup-sprog, som beskriver indholdets struktur og betydning: hvad der er en overskrift, hvad der er en knap, hvad der er et billede, og hvad der hænger sammen med hvad.

Grundopbygningen

Enhver HTML-fil starter med den samme skabelon:

<!DOCTYPE html>
<html lang="da">
<head>
  <meta charset="UTF-8">
  <title>Min side</title>
  <link rel="stylesheet" href="assets/styles/style.css">
</head>
<body>
  <!-- alt synligt indhold ligger her -->
  <script src="assets/js/script.js" defer></script>
</body>
</html>
Læg mærke til: <link> til CSS'en ligger i <head>, og <script> til JS'en ligger nederst i <body> med attributtet defer. Det sikrer at HTML'en er indlæst, før scriptet kører.

Hvad kan man bygge med det?

  • Overskrifter & tekstafsnit (h1–h6, p)
  • Links og navigation (a, nav)
  • Billeder, video og lyd (img, video, audio)
  • Lister (ul, ol, li)
  • Formularer (form, input, button)
  • Tabeller (table, tr, td)
  • Semantisk struktur (header, main, footer, section, article)
  • Menuer — dropdowns og burgermenuer (se længere nede)

Elementer, attributter og "semantisk" HTML

Et element er en start-tag, indhold og en slut-tag: <p>tekst</p>. En attribut giver ekstra info til elementet, fx href på et link eller class/id som CSS og JS senere kan gribe fat i. "Semantisk" HTML betyder at man vælger tags efter deres betydning (fx <nav> til navigation) frem for kun deres udseende — det hjælper skærmlæsere, SEO og andre udviklere med at forstå siden.

HTML-dokumentets opbygning — linje for linje

Samme skabelon som i faktaboksen ovenfor, men nu forklaret linje for linje.

<!DOCTYPE html>
<html lang="da">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Min side</title>
  <link rel="stylesheet" href="assets/styles/style.css">
</head>
<body>
  <!-- alt synligt indhold -->
  <script src="assets/js/script.js"></script>
</body>
</html>
LinjeForklaring
<!DOCTYPE html>Fortæller browseren at dette er et moderne HTML5-dokument. Skal altid stå øverst — uden den falder browseren tilbage til en gammel, upålidelig visningstilstand ("quirks mode").
<html lang="da">Roden i hele dokumentet. lang="da" fortæller skærmlæsere og browseren at indholdet er dansk — vigtigt for oplæsning og oversættelse.
<head>Usynligt "maskinrum": metadata, titel, kobling til CSS, ikoner, SEO-tags. Intet heri vises direkte på siden.
<meta charset="UTF-8">Sikrer at æ, ø, å og andre specialtegn vises korrekt, uanset hvilken computer eller browser der åbner siden.
<meta name="viewport" ...>Gør siden responsiv — uden denne linje zoomer mobiler siden ud, så den ligner en lille desktop-side.
<title>Teksten i browserfanen og i Googles søgeresultater. Én pr. side, og bør beskrive sidens indhold kort.
<link rel="stylesheet">Kobler den eksterne CSS-fil på — dette er hele grunden til at CSS kan holdes i sin egen fil.
<body>Alt det synlige indhold på siden: tekst, billeder, knapper, menuer osv.
<script src="...">Kobler JS-filen på. Ligger nederst (eller bruger defer), så HTML'en er klar, før scriptet begynder at lede efter elementer.

HTML-elementer

De mest brugte tags, og hvad de bruges til.

TagBruges til
<h1>–<h6>Overskrifter i faldende vigtighed. Kun én <h1> pr. side.
<p>Et almindeligt tekstafsnit.
<img>Indsætter et billede. Selvlukkende tag — ingen </img>.
<a>Et link — internt, eksternt, mail eller telefon (se linkafsnittet).
<button>En klikbar knap, typisk brugt sammen med JavaScript.
<div>En generisk beholder uden betydning — bruges når intet semantisk tag passer.
<span>Som div, men inline — til at style en bid tekst midt i et afsnit.
<section>Et tematisk afsnit af siden, ofte med sin egen overskrift.
<article>Selvstændigt indhold der giver mening alene — et blogindlæg, et nyhedskort.
<header>Toppen af siden eller en sektion — logo, titel, navigation.
<footer>Bunden af siden eller en sektion — copyright, links, kontaktinfo.
<nav>En samling navigationslinks.
<main>Sidens primære, unikke indhold. Kun én pr. side.
<ul> / <ol> / <li>Punktopstillet liste (usorteret) / nummereret liste / listepunkt.

Attributter

Ekstra information tilføjet inde i en tag, skrevet som navn="værdi".

AttributBruges til
classGiver elementet et navn, som CSS og JS kan style/vælge. Kan genbruges på flere elementer.
idEt unikt navn til ét bestemt element — bruges til anchor-links og direkte JS-opslag.
srcKilden/stien til et billede, script eller andet medie.
hrefDestinationen for et link.
altAlternativ tekst til et billede — vises hvis billedet ikke kan hentes, og læses op af skærmlæsere.
titleEkstra tooltip-tekst, der vises når musen holdes stille over elementet.
targetHvor et link skal åbne, fx target="_blank" for nyt faneblad.
relBeskriver forholdet til det linkede dokument, fx rel="noopener" af sikkerhedshensyn ved target="_blank".
typeAngiver typen, fx type="text" eller type="checkbox" på et input.
nameNavnet et formularfelt sendes under — bruges af serveren/JS til at læse svaret.
placeholderGrå eksempeltekst i et tomt inputfelt — forsvinder når man skriver.
valueDen aktuelle/forudfyldte værdi i et formularfelt.

Semantik: hvorfor ikke bare <div> overalt?

header, nav, main, section, article, aside og footer gør nøjagtig det samme visuelt som en div — men de fortæller browseren, skærmlæsere og søgemaskiner hvad indholdet betyder.

❌ Kun div'er

<div class="top">...</div>
<div class="menu">...</div>
<div class="content">...</div>
<div class="bottom">...</div>

✔ Semantisk HTML

<header>...</header>
<nav>...</nav>
<main>...</main>
<footer>...</footer>
Samme visuelle resultat — men den semantiske version giver bedre SEO, bedre tilgængelighed, og gør koden selvforklarende for andre udviklere (og dig selv om seks måneder).

Billeder

<img
  src="assets/images/hund.jpg"
  alt="Golden retriever der løber på en strand"
  width="800"
  height="600"
  loading="lazy"
>

<!-- picture: forskellige billeder på forskellige skærmstørrelser -->
<picture>
  <source media="(min-width: 768px)" srcset="stor.jpg">
  <img src="lille.jpg" alt="Beskrivelse">
</picture>

alt

Beskriver billedet for skærmlæsere og SEO. Skal altid være der.

width / height

Reserverer plads inden billedet er loadet — forhindrer at siden "hopper".

loading="lazy"

Udskyder indlæsning til billedet er tæt på at komme i syne — hurtigere sideindlæsning.

Lister & tabeller

Lister

<ul> <!-- usorteret -->
  <li>Mælk</li>
  <li>Brød</li>
</ul>

<ol> <!-- nummereret -->
  <li>Forvarm ovnen</li>
  <li>Bland dejen</li>
</ol>

Tabeller

<table>
  <thead>
    <tr><th>Navn</th><th>Alder</th></tr>
  </thead>
  <tbody>
    <tr><td>Anna</td><td>29</td></tr>
  </tbody>
</table>

Formularer

Sådan opsamler man input fra en bruger.

Live demo

<form>
  <label>
    Navn
    <input type="text" name="navn" placeholder="Dit navn">
  </label>

  <textarea name="besked"></textarea>

  <select name="emne">
    <option value="support">Support</option>
  </select>

  <input type="checkbox" name="nyhedsbrev">
  <input type="radio" name="niveau">

  <button type="submit">Send</button>
</form>
En rigtig formular skal desuden valideres og sendes til en server (fx via fetch) — denne demo viser kun opbygningen og forhindrer siden i at genindlæse ved submit.

Video, audio & iframe

Video

<video controls width="600">
  <source src="film.mp4" type="video/mp4">
</video>

Audio

<audio controls>
  <source src="lyd.mp3" type="audio/mpeg">
</audio>

iframe (YouTube/Maps)

<iframe
  src="https://www.youtube.com/embed/ID"
  loading="lazy"
  allowfullscreen></iframe>

autoplay, loop og muted kan tilføjes til <video> for baggrundsvideoer — browsere kræver ofte muted for at autoplay overhovedet virker.

Navigation

Grundopskriften bag al navigation — også dropdown- og burgermenuen længere nede.

<nav>
  <ul>
    <li><a href="#">Forside</a></li>
    <li><a href="#">Om os</a></li>
    <li><a href="#">Kontakt</a></li>
  </ul>
</nav>

Denne simple struktur — nav → ul → li → a — genbruges i sidebaren, dropdown-menuen og burgermenuen på denne side. Kun CSS og JS ovenpå ændrer sig.

CSS

Cascading Style Sheets

CSS er udseendet. Det bestemmer farver, skrifttyper, afstand, layout og animationer — alt det HTML'en selv ikke kan udtrykke. Navnet "cascading" (faldende) betyder, at regler kan overskrive hinanden efter bestemte prioriteringsregler (specificitet, rækkefølge, !important).

Sådan kobles CSS til HTML — kun via link

I dette projekt ligger al CSS i assets/styles/style.css og kobles på med ét tag i <head>:

<link rel="stylesheet" href="assets/styles/style.css">
Undgå: style="color:red" direkte på et element, og <style>...</style> inde i HTML'en. Det er "inline CSS" og bryder reglen om at HTML og CSS skal holdes adskilt.

De vigtigste byggeklodser

Selektorer

.klasse, #id, element — udpeger hvad reglen rammer.

Box model

margin → border → padding → indhold, i den rækkefølge udefra og ind.

Layout

display: flex og display: grid til at placere elementer i forhold til hinanden.

Variabler

:root { --farve: #000; } — genbrug værdier ét sted i stedet for at gentage dem.

Media queries

@media (max-width: 768px) — anderledes styling på mindre skærme (bruges til burgermenuen).

Pseudo-klasser

:hover, :focus, :nth-child() — styling baseret på tilstand eller position.

25 CSS-kapitler — kort forklaret

Et hurtigt opslagsværk over de emner, du støder på hele tiden.

Farver

color: #3b82f6; — tekstfarve. Brug hex, rgb() eller navngivne farver.

Baggrunde

background: url(bg.jpg) center/cover; — farve, billede eller gradient.

Margin

Luft udenfor et element, mellem det og naboerne.

Padding

Luft indeni et element, mellem kant og indhold.

Border

border: 1px solid #000; — en kant rundt om elementet.

Border-radius

border-radius: 12px; — runder hjørnerne. 50% giver en cirkel.

Display

block, inline, flex, grid, none — hvordan elementet opfører sig i layoutet.

Position

static, relative, absolute, fixed, sticky — styrer hvor elementet placeres.

Flexbox

display: flex; — arrangér elementer på én linje/kolonne, med nem centrering.

Grid

display: grid; — arrangér elementer i rækker og kolonner samtidig.

Animation

@keyframes + animation: navn 2s infinite; — flertrins bevægelse.

Transition

transition: all 0.3s ease; — bløde overgange mellem to tilstande.

Transform

transform: scale(1.1) rotate(5deg); — flyt, roter, skalér uden at påvirke layoutet.

Pseudo-klasser

:hover, :focus, :nth-child() — styling baseret på tilstand/position.

Pseudo-elementer

::before, ::after — indsætter usynligt indhold via CSS.

Variabler

:root { --brand: #f00; } så color: var(--brand); genbruges overalt.

Media queries

@media (max-width: 768px) { ... } — anderledes styling på mindre skærme.

Responsive design

Layoutet tilpasser sig automatisk skærmstørrelsen — kombinerer % / fr / media queries.

Dark mode

Skift farvetema med en CSS-klasse på <body> eller prefers-color-scheme.

Hover

a:hover { color: red; } — styling når musen holdes over.

Focus

button:focus { outline: 2px solid blue; } — styling når tastaturet har valgt elementet.

Active

button:active { transform: scale(0.97); } — styling i det øjeblik der klikkes.

Opacity

opacity: 0.5; — gennemsigtighed, 0 (usynlig) til 1 (helt synlig).

Overflow

overflow: hidden/scroll/auto; — hvad der sker når indhold er større end sin boks.

Z-index

z-index: 10; — hvilket element ligger ovenpå hvilket (kræver position).

Enheder

px fast, % relativ til forælder, em/rem relativ til skriftstørrelse, vw/vh relativ til skærmen.

JS

JavaScript

JavaScript er adfærden — det der gør siden interaktiv. Med JS kan man reagere på klik, ændre indhold uden at genindlæse siden, hente data, validere formularer og styre alt det som HTML og CSS ikke kan klare alene (som at åbne/lukke en menu ved klik).

Sådan kobles JS til HTML — kun via script-tag

<script src="assets/js/script.js" defer></script>
Undgå: <button onclick="gørNoget()"> direkte i HTML'en. Brug i stedet addEventListener inde i script.js.

Grundmønsteret du bruger 90% af tiden

// 1. find elementet i HTML'en
const knap = document.querySelector('.min-knap');

// 2. lyt efter en handling (klik, tastetryk, osv.)
knap.addEventListener('click', () => {
  // 3. gør noget — fx skift en CSS-klasse
  knap.classList.toggle('aktiv');
});

Det er præcis dette mønster — find element → lyt efter klik → toggle en class — der driver både dropdown-menuen og burgermenuen længere nede på siden.

JavaScript-koncepter, du støder på hele tiden

let / const / var

const ændrer sig aldrig, let kan ændres, var er den gamle måde — undgå den.

if / else / switch

Forgreninger: kør forskellig kode afhængigt af en betingelse.

for / while

Løkker der gentager kode, fx for hvert element i en liste.

Functions

function navn() { } — genbrugelig kodeblok du kan kalde igen og igen.

Arrow functions

const navn = () => { } — kortere skrivemåde, meget brugt i event listeners.

Objects

{ navn: "Anna", alder: 29 } — data samlet i nøgle/værdi-par.

Arrays

["æble", "pære"] — en ordnet liste af værdier.

Loops over arrays

array.forEach() / array.map() — kør kode for hvert element.

Events

click, change, submit — handlinger brugeren udfører på siden.

DOMContentLoaded

Fyres når HTML'en er indlæst — sikrer at dine querySelector-kald finder elementerne.

querySelector

Finder det første element der matcher en CSS-selektor, fx .knap.

querySelectorAll

Finder alle matchende elementer som en liste.

getElementById

Finder ét element via dets id — hurtigere, men mindre fleksibel end querySelector.

addEventListener

Lytter efter en handling og kører en funktion, når den sker.

classList

.add(), .remove(), .toggle() — tilføjer/fjerner/skifter en CSS-klasse fra JS.

fetch

Henter data fra et andet sted på nettet (et API), uden at genindlæse siden.

async / await

Gør det muligt at skrive kode, der venter på et svar (fx fra fetch), som om den var synkron.

JSON

Tekstformatet data typisk sendes i — JSON.parse() og JSON.stringify() konverterer frem og tilbage.

Modules

import / export — deler JS-koden op i flere filer, der kan genbruge hinanden.

Hvorfor holde filerne adskilt?

Adskillelse af struktur (HTML), udseende (CSS) og adfærd (JS) er ikke en formalitet — det gør projektet lettere at fejlfinde, genbruge og samarbejde om.

Genbrug

Én style.css kan style flere HTML-sider ens, uden at man kopierer kode.

Overblik

Man slipper for at lede efter styling midt i markup — hver fil har ét ansvar.

Caching

Browseren kan cache .css og .js separat, så siden loader hurtigere ved genbesøg.

Den fysiske mappestruktur i dette projekt:

Guideline/
├── index.html              # al markup / struktur
└── assets/
    ├── styles/
    │   └── style.css       # al CSS, intet i html
    └── js/
        └── script.js       # al JS, intet i html

Burgermenu

Burgermenuen (tre linjer, ☰) bruges til at skjule navigationen på små skærme og vise den i et klik. Den kombinerer alle tre sprog: HTML til ikonet, CSS til at gemme/vise + animere, og JS til at toggle en class. Prøv at gøre vinduet smallere — sidebaren i venstre side af denne side er selv en burgermenu på mobil.

Live demo — klik på ikonet

HTML — selve ikonet er tre <span>'er

<button class="burger" aria-expanded="false" aria-label="Åbn menu">
  <span></span>
  <span></span>
  <span></span>
</button>
<ul class="nav-menu">...</ul>

CSS — skjul menuen, animer linjerne til et kryds

/* menuen er skjult, indtil JS tilføjer .er-aaben */
.nav-menu { display: none; }
.nav-menu.er-aaben { display: flex; }

/* animér de tre streger til et "X" når knappen får .aktiv */
.burger.aktiv span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.burger.aktiv span:nth-child(2) { opacity: 0; }
.burger.aktiv span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* burgeren vises kun under en bestemt bredde */
@media (max-width: 768px) {
  .burger { display: block; }
}

JS — toggle to classes samtidig

const burger = document.querySelector('.burger');
const menu   = document.querySelector('.nav-menu');

burger.addEventListener('click', () => {
  const aaben = burger.classList.toggle('aktiv');
  menu.classList.toggle('er-aaben');
  burger.setAttribute('aria-expanded', aaben);
});

Accordion

En liste af overskrifter, hvor kun ét (eller flere) svar er synligt ad gangen — typisk brugt til FAQ. Der er to måder at bygge den på: en native version helt uden JS, og en JS-styret version med fuld kontrol over animation og adfærd.

Live demo — JS-styret accordion

HTML er strukturen, CSS er udseendet, og JS er adfærden/interaktiviteten.

Ja — det er en designbeslutning. Denne demo lukker de andre, når man åbner et nyt punkt.

Nej — se den native <details>-version herunder, som virker helt uden JavaScript.

JS-styret version

<div class="accordion-item">
  <button class="accordion-trigger" aria-expanded="false">
    Spørgsmål?
  </button>
  <div class="accordion-panel">
    <p>Svaret her.</p>
  </div>
</div>
.accordion-panel {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}
.accordion-panel.er-aaben {
  max-height: 200px; /* skal være stor nok til indholdet */
}

Native version — helt uden JavaScript

<details>
  <summary>Spørgsmål?</summary>
  <p>Svaret vises når man klikker på summary.</p>
</details>
<details>/<summary> er indbygget i browseren og kræver ingen CSS eller JS for at virke. Bruges også i projekt-biblioteket længere nede på siden.

Tabs

Flere indholdsblokke, hvor kun én vises ad gangen, styret af en række klikbare faner.

Live demo
Strukturen — overskrifter, tekst, billeder, links.
Udseendet — farver, layout, afstand, animation.
Adfærden — klik, toggles, hentning af data.
<div class="tabs">
  <div class="tabs-list">
    <button class="tab is-active" data-tab="html">HTML</button>
    <button class="tab" data-tab="css">CSS</button>
  </div>
  <div class="panel is-active" data-panel="html">...</div>
  <div class="panel" data-panel="css">...</div>
</div>
const tabs = document.querySelectorAll('.tab');

tabs.forEach((tab) => {
  tab.addEventListener('click', () => {
    // fjern is-active fra alle faner og paneler
    document.querySelectorAll('.tab, .panel').forEach(el => el.classList.remove('is-active'));

    // aktivér den fane der blev klikket, og dens tilhørende panel
    tab.classList.add('is-active');
    const panelSelector = `[data-panel="${tab.dataset.tab}"]`;
    document.querySelector(panelSelector).classList.add('is-active');
  });
});

Cards

Et gentaget "kort" af indhold — billede, titel, tekst, knap — arrangeret i et grid.

Live demo
HTML

Struktur

Skelettet på siden.

CSS

Udseende

Farver, layout, animation.

JS

Adfærd

Interaktivitet og logik.

<div class="cards">
  <article class="card">
    <img src="billede.jpg" alt="...">
    <h3>Titel</h3>
    <p>Kort beskrivelse.</p>
  </article>
</div>
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.25rem;
}
.card {
  background: #fff;
  border-radius: 12px;
  padding: 1.25rem;
  transition: transform 0.2s ease;
}
.card:hover { transform: translateY(-4px); }

Grid layout

CSS Grid arrangerer elementer i rækker og kolonner samtidig — perfekt til hele sidelayouts.

Live demo — 12 grid-felter
Header (span 12)
Sidebar (span 3)
Indhold (span 9)
Footer (span 12)
.layout {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 1rem;
}
.sidebar { grid-column: span 3; }
.content { grid-column: span 9; }

Hero section

Det første, besøgende ser — stor overskrift, en kort tekst og en tydelig call-to-action. Toppen af denne side er selv et eksempel på en hero-sektion.

<section class="hero">
  <h1>Stor, tydelig overskrift</h1>
  <p>Én til to sætninger, der forklarer værditilbuddet.</p>
  <a href="#kontakt" class="btn">Kom i gang</a>
</section>

SEO — de basale byggesten

title

Unik, beskrivende titel pr. side — vises som overskrift i Google.

meta description

<meta name="description" content="..."> — teksten under titlen i søgeresultater.

keywords

Har i dag minimal effekt på Google, men skader ikke at have relevante ord i indhold og titel.

lang

<html lang="da"> hjælper søgemaskiner med at vise siden til de rette brugere.

alt-tekster

Beskrivende alt på billeder giver ekstra kontekst til Google Billeder.

Overskriftshierarki

Én h1, derefter h2/h3 i rækkefølge — hjælper Google med at forstå sidens struktur.

Accessibility (tilgængelighed)

ARIA

aria-expanded, aria-label, aria-hidden — ekstra info til skærmlæsere, når HTML alene ikke er nok.

Alt-tekst

Alle informationsbærende billeder skal have en beskrivende alt.

Labels

Hvert input skal have en tilknyttet <label>, så skærmlæsere ved hvad feltet betyder.

Tab-rækkefølge

Interaktive elementer skal kunne nås i en logisk rækkefølge med Tab-tasten alene.

Keyboard navigation

Alt der virker med museklik (menuer, modaler) skal også kunne betjenes og lukkes med tastaturet (Enter, Escape).

Farvekontrast

Tekst skal have tilstrækkelig kontrast mod baggrunden til at være læselig for synshæmmede.

Projekt-bibliotek — 20 færdige guides

Dropdown-menu, burgermenu, accordion, tabs og modal er allerede gennemgået med live demo ovenfor. Herunder er opskrifter (HTML + CSS + JS) på resten. Klik på et projekt for at folde koden ud — det er selv et eksempel på en native <details>-accordion.

1. Sticky navigation

En navigationsbar der bliver hængende øverst når man scroller ned.

nav {
  position: sticky;
  top: 0;
  z-index: 100;
  background: #fff;
}
2. Dark mode-knap

Skifter hele sidens farvetema og husker valget med localStorage.

Live demo
const knap = document.getElementById('darkModeBtn');
knap.addEventListener('click', () => {
  document.body.classList.toggle('lys-tema');
  const erLyst = document.body.classList.contains('lys-tema');
  localStorage.setItem('tema', erLyst ? 'lys' : 'moerk');
});

// husk valget ved sideindlæsning
if (localStorage.getItem('tema') === 'lys') {
  document.body.classList.add('lys-tema');
}
3. Image slider / karrusel

Viser ét billede ad gangen med knapper til frem/tilbage.

let index = 0;
const billeder = document.querySelectorAll('.slide');

function visSlide(i) {
  billeder.forEach(b => b.classList.remove('aktiv'));
  billeder[i].classList.add('aktiv');
}

document.querySelector('.next').addEventListener('click', () => {
  index = (index + 1) % billeder.length;
  visSlide(index);
});
4. Kontaktformular med validering

Tjekker felterne, før formularen "sendes" (se formular-demoen ovenfor for grundstrukturen).

form.addEventListener('submit', (e) => {
  e.preventDefault();
  const email = form.email.value;
  if (!email.includes('@')) {
    alert('Skriv en gyldig e-mail');
    return;
  }
  // send data videre, fx med fetch(...)
});
5. Galleri

Et grid af billeder, ofte kombineret med et lightbox-klik (se næste punkt).

.galleri {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 0.75rem;
}
.galleri img { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 8px; }
6. Lightbox

Klik på et lille billede åbner det i stort format ovenpå resten af siden (samme princip som modalen ovenfor).

document.querySelectorAll('.galleri img').forEach(img => {
  img.addEventListener('click', () => {
    lightboxImg.src = img.src;
    lightboxOverlay.classList.add('er-aaben');
  });
});
7. Scroll-animation

Elementer toner ind, når de scroller ind i synsfeltet.

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) entry.target.classList.add('synlig');
  });
});
document.querySelectorAll('.animeret').forEach(el => observer.observe(el));
.animeret { opacity: 0; transform: translateY(20px); transition: all 0.5s ease; }
.animeret.synlig { opacity: 1; transform: translateY(0); }
8. Back-to-top-knap

Dukker op når man har scrollet ned, og scroller blødt tilbage til toppen ved klik.

Live demo — scroll ned på siden, og knappen dukker op nederst til højre
const knap = document.getElementById('backToTopBtn');

window.addEventListener('scroll', () => {
  knap.classList.toggle('synlig', window.scrollY > 400);
});

knap.addEventListener('click', () => {
  window.scrollTo({ top: 0, behavior: 'smooth' });
});
9. Loading screen

Et fuldskærms-lag der dækker siden, indtil alt er indlæst.

window.addEventListener('load', () => {
  document.querySelector('.loading-screen').classList.add('skjult');
});
.loading-screen { position: fixed; inset: 0; transition: opacity 0.4s ease; }
.loading-screen.skjult { opacity: 0; pointer-events: none; }
10. Cookie-banner

Vises ved første besøg og huskes i localStorage, så den ikke vises igen.

Live demo
if (!localStorage.getItem('cookiesAccepteret')) {
  banner.classList.add('synlig');
}

acceptKnap.addEventListener('click', () => {
  localStorage.setItem('cookiesAccepteret', 'true');
  banner.classList.remove('synlig');
});
11. Søgefelt med live-filtrering

Filtrerer en liste, mens brugeren skriver — ingen sideindlæsning.

Live demo
  • HTML
  • CSS
  • JavaScript
  • Python
  • PHP
  • SQL
input.addEventListener('input', () => {
  const soegetekst = input.value.toLowerCase();
  document.querySelectorAll('li').forEach(li => {
    const matcher = li.textContent.toLowerCase().includes(soegetekst);
    li.style.display = matcher ? '' : 'none';
  });
});
12. Mega menu

En stor dropdown med flere kolonner — samme princip som dropdown-menuen, bare med et bredere panel.

.mega-menu {
  position: absolute;
  left: 0; right: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.5rem;
}
13. Sidebar-navigation

En lodret menu i siden — se selve sidebaren på denne side som live eksempel.

.sidebar {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}
14. Login-formular

E-mail + adgangskode + "husk mig" + fejlbesked ved forkert login.

<form>
  <input type="email" name="email" required>
  <input type="password" name="adgangskode" required>
  <label><input type="checkbox"> Husk mig</label>
  <button type="submit">Log ind</button>
</form>
15. Registreringsformular

Som login, men med ekstra felter og en tjekliste af krav til adgangskoden.

const adgangskode = form.adgangskode.value;
const gyldig = adgangskode.length >= 8;
knap.disabled = !gyldig;

Cheatsheets

HTML

h1–h6Overskrifter
pAfsnit
aLink
imgBillede
ul/ol/liLister
formFormular
navNavigation
sectionAfsnit af siden

CSS

displayflex/grid/block/none
margin/paddingAfstand ude/inde
color/backgroundFarver
positionrelative/absolute/fixed
transitionBløde overgange
@mediaResponsivt design
:hover/:focusTilstande
var(--x)CSS-variabel

JavaScript

querySelectorFind ét element
addEventListenerLyt efter handling
classList.toggleSkift CSS-klasse
fetchHent data
const/letDefinér variabel
=>Arrow function
localStorageGem data i browseren
JSON.parse/stringifyData ↔ tekst

Best practice

❌ Undgå

<div>
  <div>
    <div>...</div>
  </div>
</div>

✔ Gør i stedet

<header>...</header>
<nav>...</nav>
<main>...</main>
<footer>...</footer>
  • Brug semantisk HTML. Vælg tags efter deres betydning, ikke kun deres udseende.
  • Hold CSS og JavaScript i separate filer under assets/styles og assets/js — aldrig inline.
  • Giv klasser beskrivende navne, fx .dropdown-menu frem for .d1.
  • Kommentér større kodeblokke med <!-- --> i HTML og /* */ / // i CSS/JS.
  • Indrykning: vær konsekvent — enten 2 eller 4 mellemrum, ikke blandet.
  • Undgå inline CSS og inline JavaScript (style="", onclick="") helt.
  • Test altid siden via localhost, ikke ved at dobbeltklikke filen direkte.
  • Optimér billeder og brug loading="lazy" hvor det giver mening.
  • Sørg for responsivt design med media queries, testet ned til mobil.
  • Prioritér tilgængelighed med korrekt struktur, labels og alt-tekster.

Øv dig — interaktive opgaver

Hver opgaveserie ligger på sin egen side med sin egen assets/styles/ og assets/js/. Løs opgave 1 for at låse opgave 2 op — dit fremskridt gemmes automatisk i browseren.

Sådan kører du projektet på localhost

Vælg én af metoderne herunder — begge kræver ingen internetforbindelse for selve siden.

VS Code + Live Server

Installér udvidelsen "Live Server", højreklik på index.html → "Open with Live Server". Siden åbner automatisk på noget i stil med http://127.0.0.1:5500.

Python (indbygget server)

Åbn en terminal i Guideline-mappen og kør:

python -m http.server 8000

Gå så til http://localhost:8000 i browseren.

Undgå at åbne index.html direkte som en fil (file://...) hvis projektet senere skal hente data eller bruge moduler — nogle browser-funktioner kræver en rigtig server, selv en lokal én.