// 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.
# mappestruktur for projektet
Guideline/
├── index.html
└── assets/
├── styles/
│ └── style.css
└── js/
└── script.js
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>
<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>
| Linje | Forklaring |
|---|---|
<!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.
| Tag | Bruges 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".
| Attribut | Bruges til |
|---|---|
class | Giver elementet et navn, som CSS og JS kan style/vælge. Kan genbruges på flere elementer. |
id | Et unikt navn til ét bestemt element — bruges til anchor-links og direkte JS-opslag. |
src | Kilden/stien til et billede, script eller andet medie. |
href | Destinationen for et link. |
alt | Alternativ tekst til et billede — vises hvis billedet ikke kan hentes, og læses op af skærmlæsere. |
title | Ekstra tooltip-tekst, der vises når musen holdes stille over elementet. |
target | Hvor et link skal åbne, fx target="_blank" for nyt faneblad. |
rel | Beskriver forholdet til det linkede dokument, fx rel="noopener" af sikkerhedshensyn ved target="_blank". |
type | Angiver typen, fx type="text" eller type="checkbox" på et input. |
name | Navnet et formularfelt sendes under — bruges af serveren/JS til at læse svaret. |
placeholder | Grå eksempeltekst i et tomt inputfelt — forsvinder når man skriver. |
value | Den 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>
Links — de 5 typer
| Type | Kode |
|---|---|
| Internt link | <a href="om-os.html">Om os</a> |
| Eksternt link | <a href="https://example.com" target="_blank" rel="noopener">Besøg</a> |
| Mail-link | <a href="mailto:info@firma.dk">Skriv til os</a> |
| Telefon-link | <a href="tel:+4512345678">Ring til os</a> |
| Anchor-link | <a href="#kontakt">Gå til kontakt</a> (peger på et element med id="kontakt") |
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.
<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>
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.
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">
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.
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>
<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
Dropdown-menu
En dropdown er en navigationslink, der folder en undermenu ud, når man klikker eller hover'er
over den. Strukturen er en indlejret liste — en <ul> inde i en <li>.
HTML-strukturen
<nav>
<ul>
<li><a href="#">Forside</a></li>
<li class="dropdown">
<button aria-expanded="false">Produkter</button>
<ul class="dropdown__menu">
<li><a href="#">Skabeloner</a></li>
<li><a href="#">Komponenter</a></li>
</ul>
</li>
</ul>
</nav>
CSS — skjul/vis undermenuen
.dropdown__menu {
/* skjult som standard */
display: none;
position: absolute;
}
/* vises når JS sætter denne class på */
.dropdown__menu.er-aaben {
display: block;
}
JS — toggle ved klik
const trigger = document.querySelector('.dropdown button');
const menu = document.querySelector('.dropdown__menu');
trigger.addEventListener('click', () => {
const erAaben = menu.classList.toggle('er-aaben');
trigger.setAttribute('aria-expanded', erAaben);
});
:hover? Ren CSS-hover virker ikke på
touch-skærme og er dårlig for tilgængelighed. Klik + aria-expanded virker for mus,
tastatur og skærmlæsere.
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.
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.
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.
<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');
});
});
Modal / popup
Et vindue der lægger sig ovenpå resten af siden. Skal kunne lukkes tre måder: luk-knap, klik udenfor, og Escape.
<button id="openModal">Åbn modal</button>
<div class="modal-overlay" id="modalOverlay">
<div class="modal">
<button id="closeModal">×</button>
<p>Indhold i modalen</p>
</div>
</div>
.modal-overlay { display: none; position: fixed; inset: 0; }
.modal-overlay.er-aaben { display: flex; align-items: center; justify-content: center; }
const overlay = document.getElementById('modalOverlay');
document.getElementById('openModal').addEventListener('click', () => overlay.classList.add('er-aaben'));
document.getElementById('closeModal').addEventListener('click', () => overlay.classList.remove('er-aaben'));
// luk ved klik på selve den mørke baggrund (ikke modalen selv)
overlay.addEventListener('click', (e) => {
if (e.target === overlay) overlay.classList.remove('er-aaben');
});
// luk med Escape
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') overlay.classList.remove('er-aaben');
});
Cards
Et gentaget "kort" af indhold — billede, titel, tekst, knap — arrangeret i et grid.
Struktur
Skelettet på siden.
Udseende
Farver, layout, animation.
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.
.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.
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.
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.
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.
- 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–h6 | Overskrifter |
p | Afsnit |
a | Link |
img | Billede |
ul/ol/li | Lister |
form | Formular |
nav | Navigation |
section | Afsnit af siden |
CSS
display | flex/grid/block/none |
margin/padding | Afstand ude/inde |
color/background | Farver |
position | relative/absolute/fixed |
transition | Bløde overgange |
@media | Responsivt design |
:hover/:focus | Tilstande |
var(--x) | CSS-variabel |
JavaScript
querySelector | Find ét element |
addEventListener | Lyt efter handling |
classList.toggle | Skift CSS-klasse |
fetch | Hent data |
const/let | Definér variabel |
=> | Arrow function |
localStorage | Gem data i browseren |
JSON.parse/stringify | Data ↔ 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/stylesogassets/js— aldrig inline. - Giv klasser beskrivende navne, fx
.dropdown-menufrem 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.
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.