Gå til indhold
Jan Engelbrecht Pederseb Logo

Moderne Webudvikling med Web Components

Et komplet kursus fra introduktion til Custom Elements og Shadow DOM til bygning af et fuldt funktionelt UI-bibliotek.

Moderne Webudvikling med Web Components

Et komplet kursus fra introduktion til Custom Elements og Shadow DOM til bygning af et fuldt funktionelt UI-bibliotek.

Modul 1: Introduktion til Web Components

Modul 2: Custom Elements – Lav dine egne HTML-tags

Modul 3: Shadow DOM – Isolation af kode og design

Modul 4: HTML Templates – Effektive støbeforme til kode

Modul 5: Avanceret templating med <slot>-tagget

Modul 6: Interaktivitet, Tilstand (State) og Events

Modul 7: Praktisk afgangsprojekt & Økosystemet

Modul 1: Introduktion til Web Components

Velkommen til det første modul i kurset. Før vi dykker ned i kode, skal vi forstå det fundamentale problem, som Web Components løser, og hvordan teknologien adskiller sig fra de store JavaScript-frameworks, du måske kender i dag (som React, Angular eller Vue).

1. Hvad er Web Components?

Web Components er ikke ét enkelt værktøj eller bibliotek. Det er en samling af fire native webstandarder, som W3C og browserproducenterne har indbygget direkte i browserens motor.
Formålet er simpelt: At gøre det muligt at oprette genanvendelige, indkapslede og speciallavede HTML-tags, som fungerer overalt på internettet uden eksterne afhængigheder.
I dag har du standard-tags som <video> eller <input type="date">. Bag disse tags gemmer der sig avanceret logik og styling (f.eks. knapper til at afspille, spole og justere lyd). Med Web Components kan du lave dine helt egne tags, som f.eks. <web-butik-kurv>, <bruger-kort> eller <custom-slider>, der fungerer på nøjagtig samme indbyggede måde.

2. Web Components vs. Frameworks (React, Vue, Angular)

Hvorfor overhovedet bruge Web Components, når vi allerede har populære frameworks? Svaret ligger i langtidsholdbarhed og fleksibilitet.

Egenskab Web Components (Native) Frameworks (React / Vue)
AfhængighederIngen. Koden kører direkte i alle moderne browsere.Kræver installation af biblioteker, npm-pakker og build-værktøjer.
LevetidPermanent. Webstandarder fjernes stort set aldrig. Kode skrevet i dag virker også om 15 år.Tidsbegrænset. Frameworks opdateres med store ændringer (breaking changes), og ældre kode forældes.
YdeevneMeget letvægt. Browseren skal ikke downloade og analysere et stort framework først.Tungere. Brugeren skal downloade selve framework-koden, før appen kan starte.
KompatibilitetKan bruges inde i ethvert framework (React, Vue, Angular) eller på en ren HTML-side.Kan normalt ikke blandes på tværs af hinanden (f.eks. React-komponenter inde i Angular).

Konklusion: Frameworks er fantastiske til at styre gigantiske, komplekse fullstack-applikationer med masser af dynamisk data. Web Components er derimod det ultimative valg til at bygge designsystemer og UI-biblioteker (knapper, menuer, formularer), som skal kunne genbruges på tværs af mange forskellige projekter og teknologier i en virksomhed.

3. De 4 grundpiller i Web Components

Teknologien bag Web Components hviler på fire specifikke standarder. I dette kursus vil vi dykke dybt ned i dem alle, men her er den overordnede introduktion:

  • Custom Elements (Modul 2): Det sæt af JavaScript-regler, der gør det muligt at fortælle browseren: "Hey, når du ser tagget <min-knap>, så skal du køre denne specifikke JavaScript-klasse."
  • Shadow DOM (Modul 3): En lukket, digital "boble" inden i dit HTML-element. CSS-styling og HTML-struktur inde i boblen er fuldstændig isoleret fra resten af hjemmesiden, så ekstern kode ikke kan ødelægge dit design ved et uheld.
  • HTML Templates (Modul 4): Brug af <template>-tagget til at skrive rå HTML-støbeforme, som browseren gemmer i hukommelsen uden at vise dem, indtil du vækker dem til live med JavaScript.
  • Slots (Modul 5): Brug af <slot>-tagget, som fungerer som et fleksibelt "hul" i din støbeform. Det gør det muligt for andre at putte deres egen tekst eller HTML ind i din færdige komponent udefra.

✍️ Modul 1: Quiz & Refleksion

Inden vi hopper videre til Modul 2 og begynder at skrive vores allerførste Custom Element, så overvej følgende spørgsmål:

  1. Hvis en stor virksomhed har tre forskellige web-apps (én bygget i React, én i Angular og en gammel side i ren HTML/PHP) – hvorfor vil det så være smart at bygge virksomhedens nye visuelle design-knapper som Web Components i stedet for React-komponenter?
  2. Hvad er den vigtigste regel for navngivningen af et Custom Element (f.eks. <mit-tag>), som sikrer, at browseren ikke forveksler det med fremtidige officielle HTML-tags? (Hint: Kig på eksemplerne i teksten).

Modul 2: Custom Elements – Lav dine egne HTML-tags

Nu skal vi i gang med at skrive kode! I dette modul lærer du at bruge Custom Elements, som er den første grundpille i Web Components. Det er det API, der gør det muligt at oprette dine egne HTML-tags, som browseren forstår og kan interagere med.

1. Reglen over dem alle: Navngivning med bindestreg

Før vi skriver den første linje JavaScript, skal vi have styr på navngivningen. Når du opretter et Custom Element, skal navnet altid indeholde en bindestreg (-).

  • Lovlige navne: <bruger-kort>, <super-knap>, <mads-slider>
  • Ulovlige navne: <brugerkort>, <knap>, <slider>

Hvorfor? Browser-producenterne (W3C) tilføjer løbende nye officielle HTML-tags (ligesom de i sin tid tilføjede <video> og <article>). De officielle tags vil aldrig indeholde en bindestreg. Ved at tvinge dig til at bruge en bindestreg garanterer browseren, at dit eget tag aldrig kolliderer med fremtidige standarder.

2. Grundstrukturen: Klassen og HTMLElement

For at lave et nyt tag skal du oprette en JavaScript-klasse, der udvider (arver fra) den indbyggede HTMLElement-klasse. Derefter skal du registrere det i browserens kartotek ved hjælp af customElements.define().
Her er det absolut mest simple blueprint:

// 1. Vi opretter klassen, der definerer elementets opførsel
class MinKnap extends HTMLElement {
  constructor() {
    super(); // VIGTIGT: Skal altid kaldes først, så HTMLElement bliver initialiseret!
  }

  // connectedCallback kører automatisk, når tagget indsættes på websiden
  connectedCallback() {
    this.innerHTML = "<button>Klik her!</button>";
  }
}

// 2. Vi registrerer tagget i browseren
// Det første argument er HTML-tagget, det andet er klassen ovenfor
customElements.define('min-knap', MinKnap);
            

Vær forsigtig med at bruge denne kode.

Når dette script er indlæst, kan du skrive <min-knap></min-knap> hvor som helst i din HTML-fil, og browseren vil forvandle det til en rigtig knap.

3. Komponentens livscyklus (Lifecycle Callbacks)

Et Custom Element har indbyggede metoder, der kører automatisk på specifikke tidspunkter i elementets "liv". De tre vigtigste er:

Metode Hvornår kører den? Typisk brug
connectedCallback()Når elementet bliver tilføjet til DOM'en (websiden).Her indsætter du din HTML, henter data eller opsætter event-listeners (f.eks. klikfunktioner).
disconnectedCallback()Når elementet bliver fjernet fra websiden igen.Bruges til oprydning, så din kode ikke sløver computeren (f.eks. fjerne event-listeners eller timere).
attributeChangedCallback()Når en af elementets attributter (f.eks. farve="rød") ændres.Bruges til at opdatere komponenten live, når data udefra ændrer sig.

4. Øvelse: En dynamisk profil-hilsen

Lad os bygge en komponent, der kan modtage data udefra via en HTML-attribut (f.eks. <profil-hilsen brugernavn="Peter"></profil-hilsen>).
Vi bruger this.getAttribute() inde i connectedCallback() til at læse værdien:

HTML:

<profil-hilsen brugernavn="Mette"></profil-hilsen>
<profil-hilsen brugernavn="Lars"></profil-hilsen>
<profil-hilsen></profil-hilsen> <!-- Tester fallback -->

<script src="app.js"></script>
            

JavaScript (app.js):

class ProfilHilsen extends HTMLElement {
  constructor() {
    super();
  }

  connectedCallback() {
    // 1. Hent værdien fra attributten "brugernavn"
    // Hvis attributten mangler, bruger vi 'gæst' som standard (fallback)
    const navn = this.getAttribute('brugernavn') || 'gæst';

    // 2. Generer HTML-indholdet baseret på dataen
    this.innerHTML = `
      <div class="hilsen-boks" style="border: 1px solid #ccc; padding: 10px; margin: 5px;">
        <p>Velkommen tilbage, <strong>${navn}</strong>!</p>
      </div>
    `;
  }
}

customElements.define('profil-hilsen', ProfilHilsen);
            

✍️ Modul 2: Kode-udfordring

Nu er det din tur til at prøve. Åbn en kodeeditor eller et online værktøj (som f.eks. CodePen), og løs denne opgave:

  1. Opret en komponent med navnet <advarsels-boks>.
  2. Komponenten skal tage imod en attribut, der hedder tekst.
  3. Inde i komponenten skal teksten vises i et <div>-tag, som har en rød baggrundsfarve og hvid tekst (du kan style det direkte med style="..." i din JavaScript, ligesom i eksemplet ovenfor).
💡 Løsning på kode-udfordringen (Modul 2)

Her er den komplette kode til din <advarsels-boks>. Læg mærke til, hvordan vi henter teksten dynamisk og styler elementet direkte i JavaScript.

HTML:

<advarsels-boks tekst="Systemet lukker om 5 minutter pga. opdatering!"></advarsels-boks>
<advarsels-boks tekst="Adgang nægtet: Forkert adgangskode."></advarsels-boks>
                

JavaScript:

class AdvarselsBoks extends HTMLElement {
  constructor() {
    super();
  }

  connectedCallback() {
    // Hent teksten fra attributten. Hvis tom, bruges en standardbesked.
    const besked = this.getAttribute('tekst') || 'Der opstod en ukendt fejl.';

    // Indsæt HTML og tilføj rød baggrund og hvid tekst
    this.innerHTML = `
      <div style="background-color: #d32f2f; color: white; padding: 15px; margin: 10px 0; font-family: sans-serif; border-radius: 4px; font-weight: bold;">
        ⚠️ ADVARSEL: ${besked}
      </div>
    `;
  }
}

customElements.define('advarsels-boks', AdvarselsBoks);
                

Modul 3: Shadow DOM – Isolation af kode og design

Hvis du kigger på løsningen ovenfor, kan du måske spotte et potentielt problem: Vi skriver CSS-styling direkte i JavaScript-strenge (style="..."). Hvis vi i stedet havde tilføjet et <style>-tag indeni vores komponent, ville denne CSS risikere at ramme andre elementer på hele hjemmesiden.
Det er her, den anden grundpille i Web Components kommer til undsætning: Shadow DOM.

1. Hvad er Shadow DOM?

Når du åbner en normal hjemmeside, tolker browseren den som et stort træ af elementer (HTML-tags). Dette kaldes Light DOM (det lyse/normale DOM). Alt i Light DOM kan se og påvirke hinanden. Hvis en programmør skriver p { color: blue; } i sit globale stylesheet, bliver alle p-tags på hele siden blå.
Shadow DOM (skygge-DOM'en) gør det muligt at oprette en isoleret, lukket digital boble indeni dit Custom Element.

  • Ingen styling slipper ud: CSS skrevet indeni din komponent påvirker kun din komponent. Du kan roligt skrive h1 { color: red; } uden at bekymre dig om, at resten af hjemmesidens overskrifter bliver røde.
  • Ingen styling slipper ind: Global CSS fra hjemmesiden (f.eks. fra Bootstrap eller et tilfældigt stylesheet) kan ikke trænge ind i din komponent og ødelægge dit design.

2. Sådan opretter du en Shadow DOM

Du aktiverer Shadow DOM i din komponents constructor() ved at kalde this.attachShadow(). Derefter bruger du this.shadowRoot i stedet for this til at indsætte din HTML og CSS.
Her er det grundlæggende blueprint:

class MinIsoleredeKomponent extends HTMLElement {
  constructor() {
    super();
    // 1. Opret en Shadow DOM boble. 
    // mode: 'open' betyder, at din JavaScript har adgang via this.shadowRoot
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    // 2. Vi bruger 'this.shadowRoot' i stedet for 'this'
    this.shadowRoot.innerHTML = `
      <style>
        /* Denne styling er 100% isoleret til denne komponent */
        p {
          color: purple;
          font-size: 20px;
        }
      </style>
      
      <p>Jeg er lilla, og global CSS kan ikke ændre mig!</p>
    `;
  }
}

customElements.define('isoleret-komponent', MinIsoleredeKomponent);
            

3. Avanceret styling: :host pseudo-klassen

Når du arbejder med Shadow DOM, vil du nogle gange style selve "skallen" af din komponent (altså selve <isoleret-komponent>-tagget set udefra). Til det formål bruger man en speciel CSS-selector indeni Shadow DOM, som hedder :host.

<style>
  /* Styler selve komponenten på ydersiden */
  :host {
    display: inline-block;
    border: 2px dashed black;
    padding: 10px;
  }
  
  /* Styler kun komponenten, hvis den har klassen .aktiv (f.eks. <isoleret-komponent class="aktiv">) */
  :host(.aktiv) {
    background-color: yellow;
  }
</style>
            

4. Open vs. Closed Mode: Hvad skal man vælge?

Når du kalder this.attachShadow(), skal du altid sende et objekt med { mode: 'open' } eller { mode: 'closed' }.

Mode Beskrivelse Anbefaling
'open'Gør det muligt at tilgå komponentens indre via element.shadowRoot fra ekstern JavaScript.Standardvalg. Bruges i 99% af alle Web Components, da det gør komponenten nemmere at teste og interagere med.
'closed'Låser komponenten fuldstændigt. Ekstern JavaScript får null, hvis de forsøger at kigge ind i skygge-DOM'en.Bruges sjældent. Kun hvis du bygger ekstremt følsomme komponenter (f.eks. login- eller betalingsfelter), hvor ingen må kunne pille ved koden udefra.

✍️ Modul 3: Kode-udfordring

Nu skal vi kombinere Modul 2 og Modul 3. Din opgave er at bygge et genanvendeligt produktkort:

  1. Opret en komponent med navnet <produkt-kort>.
  2. Giv den to attributter: titel og pris.
  3. Aktivér Shadow DOM i din constructor.
  4. Inde i din Shadow DOM skal du tilføje et <style>-tag, der styler et <h1>-tag til at være grønt.
  5. Indsæt titlen i et <h1>-tag og prisen i et <p>-tag.
  6. Test din isolation: Prøv at lave et normalt <h1>-tag i din HTML-fil uden for komponenten, og se om det forbliver sort/normalt, selvom dit produktkort gør sine egne overskrifter grønne!
💡 Løsning på kode-udfordringen (Modul 3)

Her ser du, hvordan man isolerer sit design fuldstændigt ved hjælp af Shadow DOM. Læg mærke til, hvordan det eksterne <h1>-tag i Light DOM forbliver uberørt af komponentens grønne styling.

HTML:

<!-- Et normalt globalt h1-tag for at teste isolationen -->
<h1>Jeg er en almindelig overskrift (bliver IKKE grøn)</h1>

<!-- Vores speciallavede produktkort -->
<produkt-kort titel="Trådløse Høretelefoner" pris="599 kr."></produkt-kort>
<produkt-kort titel="Mekanisk Tastatur" pris="850 kr."></produkt-kort>

<script src="app.js"></script>
                

JavaScript (app.js):

class ProduktKort extends HTMLElement {
  constructor() {
    super();
    // 1. Opret en åben Shadow DOM-boble
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    // 2. Hent værdier fra attributterne
    const titel = this.getAttribute('titel') || 'Ukendt produkt';
    const pris = this.getAttribute('pris') || '0 kr.';

    // 3. Indsæt HTML og isoleret CSS direkte i shadowRoot
    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
          border: 1px solid #ddd;
          padding: 15px;
          margin: 10px 0;
          font-family: sans-serif;
          border-radius: 8px;
          max-width: 300px;
        }
        h1 {
          color: #2e7d32; /* Grøn farve */
          font-size: 20px;
          margin: 0 0 10px 0;
        }
        p {
          color: #555;
          margin: 0;
        }
      </style>
      
      <h1>${titel}</h1>
      <p>Pris: <strong>${pris}</strong></p>
    `;
  }
}

customElements.define('produkt-kort', ProduktKort);
                

Modul 4: HTML Templates – Effektive støbeforme til kode

Selvom vi nu har isoleret vores CSS med Shadow DOM, er der stadig en ting, der er ret upraktisk i Modul 3: Vi skriver al vores HTML og CSS som en stor tekststreng direkte i JavaScript (this.shadowRoot.innerHTML = ...).
Det giver ingen autoudfyldelse (syntax highlighting) i din kode-editor, og det er nemt at lave stavefejl. Løsningen på dette er den tredje grundpille: <template>-tagget.

1. Hvad er et <template>-tag?

Et <template>-tag er et indbygget HTML-tag, der fungerer som en usynlig støbeform. Når browseren indlæser en webside og ser et <template>-tag, gør den følgende:

  1. Den læser og analyserer koden (HTML og CSS) indeni tagget, så det ligger klar i computerens hukommelse.
  2. Den viser det IKKE på skærmen.
  3. Den afvikler ikke scripts og henter ikke billeder, der ligger derinde endnu.

Det ligger bare passivt og venter på, at du med JavaScript tager en kopi af det og vækker det til live.

2. Sådan bruger du en template med en Web Component

Når du kombinerer en Web Component med en template, flytter du designet over i din HTML-fil og lader din JavaScript-klasse stå for det funktionelle.
Her er opskriften i to trin:

Trin 1: Definer støbeformen i HTML
Du placerer dit <template> direkte i dit HTML-dokument. Giv det et unikt id, så vi kan finde det senere.

<template id="min-skabelon">
  <style>
    /* Isoleret styling, der automatisk følger med templaten */
    .boks {
      background-color: #f0f0f0;
      padding: 20px;
      border-radius: 10px;
    }
  </style>

  <div class="boks">
    <h2 id="titel">Standard Titel</h2>
    <p>Dette indhold er genanvendeligt og lynhurtigt for browseren at indlæse.</p>
  </div>
</template>

<!-- Her indsætter vi vores komponent -->
<min-skabelon-komponent></min-skabelon-komponent>
            

Trin 2: Klon templaten i JavaScript
I din JavaScript-klasse henter du templaten via dens ID, tager en dyb kopi med .cloneNode(true), og klistrer den ind i din Shadow DOM.

class MinSkabelonKomponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    // 1. Find templaten i HTML-dokumentet
    const template = document.getElementById('min-skabelon');

    // 2. Lav en nøjagtig kopi (klon) af templaten. 
    // Argumentet 'true' betyder en "dyb" kloning, så alle under-tags følger med.
    const klon = template.content.cloneNode(true);

    // 3. Indsæt kopien direkte i din Shadow DOM
    this.shadowRoot.appendChild(klon);
  }

  connectedCallback() {
    // 4. Nu kan du nemt finde elementer indeni din klonede shadowRoot og ændre dem
    this.shadowRoot.getElementById('titel').textContent = "Overskrift ændret via JavaScript!";
  }
}

customElements.define('min-skabelon-komponent', MinSkabelonKomponent);
            

3. Hvorfor er <template> bedre end innerHTML?

  • Renere kode: Du skriver din HTML i HTML-filer og din JavaScript i JavaScript-filer. Din editor giver dig fuld farvemarkering og hjælp.
  • Højere ydeevne (Performance): Hver gang du bruger innerHTML = "...", skal browseren starte forfra med at analysere tekststrengen og bygge HTML-elementer ud af den. Med et <template>-tag har browseren allerede analyseret strukturen én gang for alle. At klone en template er tusind gange hurtigere for computeren end at læse en tekststreng.

✍️ Modul 4: Kode-udfordring

Nu skal du opgradere din viden. Du skal bygge et "bruger-id-kort" ved hjælp af en HTML-template:

  1. Opret et <template> i din HTML-fil med et valgfrit ID. Giv det en simpel CSS-styling indeni (f.eks. en grå ramme og en pæn skrifttype).
  2. Templaten skal indeholde en tom plads til et navn (f.eks. et <h2 id="navn-felt"></h2>) og en mail (f.eks. en <p id="mail-felt"></p>).
  3. Opret en Web Component med navnet <bruger-id>.
  4. I komponenten skal du aktivere Shadow DOM og klone din template ind i den.
  5. Giv komponenten to attributter: navn og email.
  6. Brug connectedCallback() til at hente attributterne og indsætte dem på de rigtige pladser (navn-felt og mail-felt) i din klonede shadowRoot.
💡 Løsning på kode-udfordringen (Modul 4)

Her ser du, hvordan vi skiller design og HTML helt ad fra vores JavaScript ved hjælp af en <template>.

HTML:

<!-- 1. Vores støbeform/skabelon -->
<template id="bruger-kort-skabelon">
  <style>
    :host {
      display: inline-block;
      font-family: Arial, sans-serif;
      background: #ffffff;
      border: 2px solid #0076d6;
      border-radius: 6px;
      padding: 15px;
      margin: 10px;
      box-shadow: 0 4px 6px rgba(0,0,0,0.1);
      min-width: 200px;
    }
    h2 {
      margin: 0 0 5px 0;
      color: #333;
      font-size: 18px;
    }
    p {
      margin: 0;
      color: #0076d6;
      font-size: 14px;
    }
  </style>

  <div>
    <h2 id="navn-felt"></h2>
    <p id="mail-felt"></p>
  </div>
</template>

<!-- 2. Brug af vores Web Component på siden -->
<bruger-id navn="Anna Hansen" email="anna@example.com"></bruger-id>
<bruger-id navn="Thomas Poulsen" email="thomas@example.com"></bruger-id>

<script src="app.js"></script>
                

JavaScript (app.js):

class BrugerId extends HTMLElement {
  constructor() {
    super();
    // Opret en åben Shadow DOM
    this.attachShadow({ mode: 'open' });

    // Hent og klon vores template
    const template = document.getElementById('bruger-kort-skabelon');
    const klon = template.content.cloneNode(true);

    // Indsæt klonen i Shadow DOM
    this.shadowRoot.appendChild(klon);
  }

  connectedCallback() {
    // Hent data fra attributterne
    const navn = this.getAttribute('navn') || 'Navn ikke angivet';
    const email = this.getAttribute('email') || 'E-mail ikke angivet';

    // Find de tomme pladser i vores klonede Shadow DOM og indsæt dataen
    this.shadowRoot.getElementById('navn-felt').textContent = navn;
    this.shadowRoot.getElementById('mail-felt').textContent = email;
  }
}

customElements.define('bruger-id', BrugerId);
                

Modul 5: Avanceret templating med <slot>-tagget

I Modul 4 lærte vi at sende rå tekst ind i en komponent via attributter som navn="...". Men hvad nu hvis du vil sende hel HTML-kode (f.eks. et billede, et link, eller fede formaterede tekster) ind i din komponent? Det kan attributter ikke klare.
Løsningen er <slot>-tagget (et "hul" eller en "sprække"). Det lader dig placere et tomt felt i din skabelon, som brugeren kan proppe sit eget HTML-indhold ind i udefra.

1. Hvad er et <slot>?

Forestil dig en billedramme. Selve rammen og glasset er lavet på forhånd (det er din Web Component), men du kan skifte billedet indeni ud, lige når du har lyst. Billedet er det indhold, du putter ind i dit <slot>.
Der findes to måder at bruge slots på: Unavngivne slots (standard) og Navngivne slots.

2. Standard-slots (Uden navn)

Hvis du bare har ét enkelt sted i din komponent, hvor der skal kunne indsættes fri HTML, bruger du et standard-slot uden navn.

HTML-skabelon:

<template id="kort-skabelon">
  <div style="border: 1px solid #333; padding: 20px;">
    <h2>Kort-titel</h2>
    <!-- Her laver vi et hul i skabelonen -->
    <slot></slot> 
  </div>
</template>
            

Sådan bruger du det på websiden:
Nu skriver du bare dit HTML-indhold indeni dit speciallavede tag i din normale HTML-fil:

<min-kort-komponent>
  <p>Dette er <strong>fri HTML</strong>, som bliver skubbet ind i slottet!</p>
  <button>Klik på mig</button>
</min-kort-komponent>
            

Når browseren tegner siden, tager den automatisk alt det, der står mellem <min-kort-komponent> og </min-kort-komponent>, og projekterer det direkte ind på <slot>-taggets plads inde i din Shadow DOM.

3. Navngivne slots (Named Slots)

Hvad nu hvis din skabelon har flere forskellige steder, hvor der skal indsættes fri HTML? (F.eks. ét sted til en overskrift, ét sted til et billede og ét sted til en beskrivelse). Her bruger du attributten name="...".

Skabelonen i HTML (Støbeformen):

<template id="artikel-skabelon">
  <article style="border-bottom: 2px solid #ccc; padding: 10px;">
    <!-- Hul til overskriften -->
    <slot name="overskrift"></slot>
    
    <hr>
    
    <!-- Hul til selve artiklens indhold -->
    <slot name="indhold"></slot>
  </article>
</template>
            

Sådan bruger du det på websiden:
Du fortæller nu dine HTML-tags, hvilket hul de skal flyve ind i ved at bruge slot="..."-attributten på ydersiden:

<artikel-komponent>
  <h1 slot="overskrift">Breaking News: Web Components er geniale!</h1>
  <p slot="indhold">Nye undersøgelser viser, at programmører elsker slots...</p>
</artikel-komponent>
            

JavaScript-koden bagved er præcis den samme, som vi lavede i Modul 4 (vi skal bare klone templaten ind i vores Shadow DOM i constructor). Browseren regner selv ud, hvilket tag der hører til hvilket slot baseret på navnet!

4. Default indhold (Fallback)

Hvad sker der, hvis en person glemmer at udfylde et slot? Du kan indsætte standardindhold (fallback-tekst) direkte indeni dit <slot>-tag. Det vises kun, hvis slottet forbliver tomt.

<!-- Hvis brugeren ikke angiver en overskrift, vises teksten 'Standard overskrift' -->
<slot name="overskrift">Standard overskrift</slot>
            

✍️ Modul 5: Kode-udfordring

Det er tid til at bygge en super fleksibel "info-boks" ved hjælp af navngivne slots:

  1. Opret en template i din HTML-fil med to navngivne slots: <slot name="ikon"></slot> og <slot name="tekst"></slot>.
  2. Opret en Web Component, der aktiverer Shadow DOM og kloner denne template (præcis som i løsningen fra Modul 4).
  3. Style komponenten i din template, så ikonet og teksten står pænt ved siden af hinanden (f.eks. ved at bruge en display: flex; i din CSS i templaten).
  4. Afprøv din komponent på websiden ved at sende et emoji-ikon (f.eks. <span>💡</span> eller en rigtig <img>) ind i dit ikon-slot, og en længere formateret tekst ind i dit tekst-slot.
💡 Løsning på kode-udfordringen (Modul 5)

Her ser du, hvordan vi bruger navngivne slots til at sende hel HTML-kode (herunder en emoji og formateret tekst) direkte ind i vores skabelon.

HTML:

<!-- 1. Skabelonen med navngivne slots og Flexbox styling -->
<template id="info-boks-skabelon">
  <style>
    :host {
      display: block;
      font-family: system-ui, sans-serif;
      background: #e3f2fd;
      border-left: 6px solid #2196f3;
      padding: 15px;
      margin: 15px 0;
      border-radius: 4px;
    }
    .container {
      display: flex;
      align-items: center;
      gap: 15px;
    }
    .ikon-omraade {
      font-size: 24px;
      display: flex;
      align-items: center;
    }
    .tekst-omraade {
      flex: 1;
      color: #0d47a1;
    }
  </style>

  <div class="container">
    <div class="ikon-omraade">
      <slot name="ikon">ℹ️</slot> <!-- Standardikon, hvis intet angives -->
    </div>
    <div class="tekst-omraade">
      <slot name="tekst">Standard beskedtekst...</slot>
    </div>
  </div>
</template>

<!-- 2. Brug af den fleksible komponent på websiden -->
<info-boks>
  <span slot="ikon">💡</span>
  <p slot="tekst">Dette er et <strong>vigtigt tip!</strong> Vi har sendt formateret HTML direkte ind i komponenten via slots.</p>
</info-boks>

<script src="app.js"></script>
                

JavaScript (app.js):

class InfoBoks extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    const template = document.getElementById('info-boks-skabelon');
    this.shadowRoot.appendChild(template.content.cloneNode(true));
  }
}
customElements.define('info-boks', InfoBoks);
                

Modul 6: Interaktivitet, tilstand (state) og events

Nu skal vi gøre vores Web Components interaktive! Indtil videre har vores komponenter været statiske udstillingsvinduer. I dette modul lærer du at håndtere brugerinteraktion (f.eks. klik), styre intern data (state) og sende beskeder tilbage til resten af hjemmesiden via Custom Events.

1. Event Listeners i Shadow DOM

Når du vil lytte efter begivenheder (som et klik på en knap) indeni en komponent med Shadow DOM, skal du tilføje din addEventListener inde i connectedCallback(). Det skyldes, at elementerne i din template skal være sat ind på siden, før JavaScript kan finde dem.
Her er det vigtigt at binde funktionen korrekt, så JavaScript ved, hvad this refererer til:

class KlikKnap extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `<button id="minKnap">Klik mig!</button>`;
  }

  connectedCallback() {
    // Find knappen inde i din Shadow DOM og lyt efter klik
    this.shadowRoot.getElementById('minKnap').addEventListener('click', () => this.haandterKlik());
  }

  haandterKlik() {
    alert('Du klikkede på knappen inde i skyggen!');
  }
}
customElements.define('klik-knap', KlikKnap);
            

Vær forsigtig med at bruge denne kode.

(Bemærk: Ved at bruge en arrow-function () => this.haandterKlik() sikrer vi, at this stadig peger på hele vores Web Component, og ikke kun på selve knappen).

2. State Management (Håndtering af data)

"State" er blot et fint ord for den interne data eller tilstand, som din komponent husker på. Hvis du f.eks. bygger en indkøbskurv, er din state "antallet af varer".
Når staten ændrer sig, skal vi manuelt sørge for at opdatere det, brugeren ser på skærmen (i modsætning til React, som gør dette automatisk):

class TællerKomponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    
    // Initialiser vores state (data)
    this.antal = 0;

    this.shadowRoot.innerHTML = `
      <div>
        <p>Antal: <span id="tal">0</span></p>
        <button id="plusKnap">+1</button>
      </div>
    `;
  }

  connectedCallback() {
    this.shadowRoot.getElementById('plusKnap').addEventListener('click', () => {
      this.antal++; // 1. Opdater vores state
      this.opdaterVisning(); // 2. Fortæl browseren, at den skal opdatere skærmen
    });
  }

  opdaterVisning() {
    // Find det specifikke felt og opdater teksten live
    this.shadowRoot.getElementById('tal').textContent = this.antal;
  }
}
customElements.define('min-taeller', TællerKomponent);
            

3. Custom Events – Tal med resten af appen

Nogle gange sker der noget indeni din komponent, som resten af hjemmesiden skal have besked om. Hvis en bruger f.eks. klikker "Køb" indeni et <produkt-kort>, skal den overordnede hjemmeside opdatere indkøbskurven øverst på siden.
Til det bruger man CustomEvent og this.dispatchEvent():

haandterKoeb() {
  // 1. Opret en speciallavet besked/event
  const koebEvent = new CustomEvent('produkt-koebt', {
    detail: { produktId: 42, pris: 199 }, // Send data med i 'detail'
    bubbles: true,  // Tillad at eventet bobler op igennem HTML-træet
    composed: true  // VIGTIGT: Tillader eventet at bryde ud af Shadow DOM-boblen!
  });

  // 2. Affyr begivenheden ud i verden
  this.dispatchEvent(koebEvent);
}
            

Nu kan programmøren på hovedsiden lytte efter dit event på helt normal vis:

const kort = document.querySelector('produkt-kort');
kort.addEventListener('produkt-koebt', (e) => {
  console.log(`Brugeren købte produktet med ID: ${e.detail.produktId}`);
});
            

✍️ Modul 6: Kode-udfordring

Nu skal du bygge en interaktiv kommentar-komponent, hvor en bruger kan synes godt om (like) en kommentar:

  1. Opret en template til en <bruger-kommentar>. Den skal have et navn (via et slot eller en attribut), en kommentar-tekst og en "Synes godt om"-knap.
  2. Vis antallet af likes på knappen eller i en tekst (f.eks. "Likes: 0").
  3. Aktivér Shadow DOM og opsæt en event listener i connectedCallback(), så antallet af likes stiger med 1, hver gang der klikkes på knappen.
  4. Ekstra udfordring: Hver gang der klikkes, skal din komponent affyre et Custom Event kaldet like-ændret, som sender det nye samlede antal likes med i detail-objektet.
💡 Løsning på kode-udfordringen (Modul 6)

Her ser du, hvordan vi styrer intern tilstand (likes) og bruger composed: true til at lade vores Custom Event bryde ud af Shadow DOM, så hovedsiden kan lytte til det.

HTML:

<!-- Skabelon til kommentaren -->
<template id="kommentar-skabelon">
  <style>
    :host {
      display: block;
      font-family: sans-serif;
      border: 1px solid #e0e0e0;
      padding: 15px;
      margin: 10px 0;
      border-radius: 6px;
      background: #fafafa;
      max-width: 400px;
    }
    .navn { font-weight: bold; color: #333; margin-bottom: 5px; }
    .tekst { color: #555; margin-bottom: 12px; }
    button {
      background: #0076d6; color: white; border: none;
      padding: 8px 12px; border-radius: 4px; cursor: pointer;
    }
    button:hover { background: #00569d; }
  </style>

  <div>
    <div class="navn"><slot name="forfatter">Anonym</slot></div>
    <div class="tekst"><slot name="indhold">Ingen tekst angivet...</slot></div>
    <button id="likeKnap">👍 Synes godt om (<span id="likeTal">0</span>)</button>
  </div>
</template>

<!-- Brug af komponenten -->
<bruger-kommentar id="kommentar1">
  <span slot="forfatter">Marie Thomsen</span>
  <span slot="indhold">Web Components med slots og events er jo mega smart!</span>
</bruger-kommentar>

<script src="app.js"></script>
                

JavaScript (app.js):

class BrugerKommentar extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    const template = document.getElementById('kommentar-skabelon');
    this.shadowRoot.appendChild(template.content.cloneNode(true));

    // Intern tilstand (State)
    this.likes = 0;
  }

  connectedCallback() {
    // Lyt efter klik på knappen inde i Shadow DOM
    this.shadowRoot.getElementById('likeKnap').addEventListener('click', () => this.haandterLike());
  }

  haandterLike() {
    this.likes++;
    
    // Opdater visningen i Shadow DOM
    this.shadowRoot.getElementById('likeTal').textContent = this.likes;

    // Affyr Custom Event, som kan høres på hovedsiden (Light DOM)
    const event = new CustomEvent('like-aendret', {
      detail: { samletLikes: this.likes },
      bubbles: true,
      composed: true // Gør det muligt at krydse Shadow DOM-grænsen
    });
    this.dispatchEvent(event);
  }
}

customElements.define('bruger-kommentar', BrugerKommentar);

// Eksternt script, der lytter efter vores Custom Event på hovedsiden
document.getElementById('kommentar1').addEventListener('like-aendret', (e) => {
  console.log(`Hovedsiden opdaget: Nyt antal likes er ${e.detail.samletLikes}`);
});
                

Modul 7: Praktisk afgangsprojekt & Økosystemet

Du har nu tygget dig igennem alle de grundlæggende byggeklodser: Custom Elements, Shadow DOM, HTML Templates og Slots. I dette sidste modul skal vi se på, hvordan man samler disse teknologier i den virkelige verden for at bygge et komponent-bibliotek, og hvordan det moderne økosystem ser ud.

1. Hvordan distribueres et Web Component-bibliotek?

Når store virksomheder (som f.eks. Adobe Spectrum, Salesforce eller Shoelace) bygger deres designsystemer som Web Components, pakker de dem typisk som JavaScript-moduler (ES Modules).
I stedet for at have <template>-tags liggende i en lang index.html-fil, skriver man ofte HTML og CSS direkte ind i JavaScript-filen ved hjælp af browserens indbyggede document.createElement eller CSSStyleSheet API'er (såkaldte Constructable Stylesheets). Det betyder, at en anden udvikler blot skal importere én enkelt JavaScript-fil for at få adgang til alle dine tags:

<!-- Alt en anden udvikler skal gøre for at bruge dit bibliotek -->
<script type="module" src="https://mitfirma.dk"></script>

<firma-knap variant="primary">Log ind</firma-knap>
<firma-modal boks-titel="Velkommen"></firma-modal>
            

Vær forsigtig med at bruge denne kode.

2. Introduktion til Lit (Googles letvægtsbibliotek)

Selvom du nu ved, hvordan man skriver alt i ren ("vanilla") JavaScript, vil du i den virkelige verden ofte støde på et bibliotek, der hedder Lit (tidligere LitElement), som er udviklet af Google.
Lit er ikke et tungt framework som React. Det er blot en super tynd og lynhurtig overbygning (omkring 5 KB) oven på de præcise Web Components-standarder, du lige har lært. Lit fjerner alt det "kedelige" gentagne arbejde (boilerplate-kode).
Her er en sammenligning af, hvordan vores klik-tæller fra Modul 6 ser ud, hvis man skriver den i Lit:

import { LitElement, html, css } from 'lit';

export class MinLitTaeller extends LitElement {
  // 1. Lit klarer automatisk Shadow DOM og overvågning af 'state'
  static properties = {
    antal: { type: Number }
  };

  constructor() {
    super();
    this.antal = 0;
  }

  // 2. Isoleret CSS skrives i et rent, optimeret format
  static styles = css`
    button { background: #0076d6; color: white; padding: 10px; }
  `;

  // 3. Når 'this.antal' ændrer sig, gen-tegner Lit automatisk kun den præcise del
  render() {
    return html`
      <button @click=${() => this.antal++}>
        Klikket: ${this.antal} gange
      </button>
    `;
  }
}
customElements.define('lit-taeller', MinLitTaeller);
            

Vær forsigtig med at bruge denne kode.

Som du kan se, minder Lit utrolig meget om React, men under motorhjelmen spytter det ægte, standardiserede Web Components ud, som din browser forstår uden en stor compiler.

🏆 Afgangsprojekt: Dit eget UI-Bibliotek

For at fuldføre dette kursus og kalde dig selv Web Components-udvikler, er dit afgangsprojekt at bygge et mini UI-bibliotek bestående af tre genanvendelige komponenter, som du pakker i en samlet ui-bibliotek.js fil:

  1. <ui-knap>: En knap, der ændrer farve baseret på en attribut (f.eks. type="success" eller type="danger").
  2. <ui-accordion>: Et modul med en klikbar titel (Slot 1), som folder sig ud og viser et tekstområde (Slot 2), når man klikker på den.
  3. <ui-modal>: En pop-up boks, der ligger skjult, indtil man ændrer en attribut eller kalder en metode (f.eks. element.vis()), og som har en "Luk"-knap indbygget.
💡 Løsning på afgangsprojektet

Her er den komplette løsning på dit afgangsprojekt. Koden er samlet i én enkelt JavaScript-fil (ui-bibliotek.js), som indeholder alle tre komponenter. For at gøre biblioteket uafhængigt af en ekstern HTML-fil, bruger vi Constructable Stylesheets og opretter vores HTML direkte i JavaScript via innerHTML inde i vores Shadow DOM.

📦 Filen: ui-bibliotek.js

// ==========================================
// 1. KOMPONENT: <ui-knap>
// ==========================================
class UiKnap extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    // Hent typen (success, danger eller standard)
    const type = this.getAttribute('type') || 'standard';
    
    // Bestem baggrundsfarve baseret på typen
    let farve = '#0076d6'; // Standard blå
    if (type === 'success') farve = '#2e7d32'; // Grøn
    if (type === 'danger') farve = '#c62828';  // Rød

    this.shadowRoot.innerHTML = `
      <style>
        button {
          background-color: ${farve};
          color: white;
          border: none;
          padding: 10px 20px;
          font-size: 16px;
          font-family: sans-serif;
          border-radius: 4px;
          cursor: pointer;
          transition: opacity 0.2s;
        }
        button:hover {
          opacity: 0.9;
        }
      </style>
      <button><slot></slot></button>
    `;
  }
}
customElements.define('ui-knap', UiKnap);


// ==========================================
// 2. KOMPONENT: <ui-accordion>
// ==========================================
class UiAccordion extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    
    // Intern tilstand: Er den åben eller lukket?
    this.erAaben = false;

    this.shadowRoot.innerHTML = `
      <style>
        :host {
          display: block;
          font-family: sans-serif;
          border: 1px solid #ddd;
          border-radius: 4px;
          margin: 10px 0;
          overflow: hidden;
        }
        .header {
          background: #f5f5f5;
          padding: 15px;
          cursor: pointer;
          font-weight: bold;
          display: flex;
          justify-content: space-between;
          user-select: none;
        }
        .indhold {
          padding: 15px;
          display: none;
          border-top: 1px solid #ddd;
          background: white;
        }
        :host(.aktiv) .indhold {
          display: block;
        }
      </style>
      
      <div class="header" id="trigger">
        <slot name="titel">Klik for at åbne</slot>
        <span id="pil">▼</span>
      </div>
      <div class="indhold">
        <slot name="tekst"></slot>
      </div>
    `;
  }

  connectedCallback() {
    this.shadowRoot.getElementById('trigger').addEventListener('click', () => this.toggle());
  }

  toggle() {
    this.erAaben = !this.erAaben;
    const pil = this.shadowRoot.getElementById('pil');
    
    if (this.erAaben) {
      this.classList.add('aktiv');
      pil.textContent = '▲';
    } else {
      this.classList.remove('aktiv');
      pil.textContent = '▼';
    }
  }
}
customElements.define('ui-accordion', UiAccordion);


// ==========================================
// 3. KOMPONENT: <ui-modal>
// ==========================================
class UiModal extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    this.shadowRoot.innerHTML = `
      <style>
        .backdrop {
          position: fixed;
          top: 0; left: 0; width: 100vw; height: 100vh;
          background: rgba(0, 0, 0, 0.5);
          display: none;
          justify-content: center;
          align-items: center;
          z-index: 1000;
          font-family: sans-serif;
        }
        .vindue {
          background: white;
          padding: 20px;
          border-radius: 8px;
          min-width: 300px;
          max-width: 500px;
          box-shadow: 0 4px 15px rgba(0,0,0,0.2);
        }
        .header {
          display: flex;
          justify-content: space-between;
          align-items: center;
          margin-bottom: 15px;
          font-size: 18px;
          font-weight: bold;
        }
        .luk-knap {
          background: none; border: none; font-size: 20px; cursor: pointer;
        }
        /* Når attributten 'aaben' er til stede, viser vi modulen */
        :host([aaben]) .backdrop {
          display: flex;
        }
      </style>
      
      <div class="backdrop" id="bg">
        <div class="vindue">
          <div class="header">
            <span id="titel-felt">Besked</span>
            <button class="luk-knap" id="lukKors">×</button>
          </div>
          <div class="indhold">
            <slot></slot>
          </div>
        </div>
      </div>
    `;
  }

  connectedCallback() {
    // Sæt titlen baseret på en attribut
    this.shadowRoot.getElementById('titel-felt').textContent = this.getAttribute('boks-titel') || 'Besked';

    // Luk-events
    this.shadowRoot.getElementById('lukKors').addEventListener('click', () => this.luk());
    this.shadowRoot.getElementById('bg').addEventListener('click', (e) => {
      if (e.target.id === 'bg') this.luk(); // Luk kun hvis man klikker på baggrunden, ikke selve vinduet
    });
  }

  // Offentlige metoder som hovedsiden kan kalde
  vis() {
    this.setAttribute('aaben', '');
  }

  luk() {
    this.removeAttribute('aaben');
  }
}
customElements.define('ui-modal', UiModal);
                

💻 Sådan tester du dit nye bibliotek (index.html)
Opret en HTML-fil i samme mappe og indsæt følgende kode for at se alle dine tre færdige komponenter spille sammen.

<!DOCTYPE html>
<html lang="da">
<head>
  <meta charset="UTF-8">
  <title>Test af Web Components UI-Bibliotek</title>
</head>
<body>

  <h1>Mit Web Components UI-Bibliotek</h1>
  <hr>

  <h3>1. UI Knapper</h3>
  <ui-knap>Standard Knap</ui-knap>
  <ui-knap type="success">Gem ændringer</ui-knap>
  <ui-knap type="danger">Slet alt</ui-knap>

  <hr>

  <h3>2. UI Accordions</h3>
  <ui-accordion>
    <span slot="titel">Hvad er fordelen ved Web Components?</span>
    <p slot="tekst">De kører native i browseren, kræver ingen eksterne frameworks og overlever tidens tand.</p>
  </ui-accordion>

  <ui-accordion>
    <span slot="titel">Klik her for at se mere kode</span>
    <p slot="tekst">Dette indhold ligger gemt i et navngivet slot indtil der klikkes på overskriften.</p>
  </ui-accordion>

  <hr>

  <h3>3. UI Modal (Pop-up)</h3>
  <ui-knap id="modalTrigger">Åbn Pop-up Vindue</ui-knap>

  <!-- Modalen er lukket som standard, da den ikke har attributten 'aaben' påsat -->
  <ui-modal id="minModal" boks-titel="Vigtig information">
    <p>Dette er indholdet inde i modulen!</p>
    <p>Du har officielt gennemført og bestået dit afgangsprojekt i Web Components. 🎉</p>
  </ui-modal>

  <!-- Indlæs dit bibliotek -->
  <script src="ui-bibliotek.js"></script>

  <!-- Logik til at åbne modulen via JavaScript metoden -->
  <script>
    const knap = document.getElementById('modalTrigger');
    const modal = document.getElementById('minModal');

    knap.addEventListener('click', () => {
      modal.vis(); // Kalder den offentlige .vis() metode på vores Web Component!
    });
  </script>

</body>
</html>
                

Web Components vs. JavaScript Frameworks

Valget mellem Web Components og JavaScript-frameworks (som React, Vue og Angular) handler sjældent om at vælge den "bedste" teknologi, men derimod om at vælge det rigtige værktøj til den specifikke opgave.

Kort sagt: Web Components er bygget til genanvendelighed og lang levetid, mens frameworks er bygget til kompleks forretningslogik og store applikationer.

Overblik: Det hurtige valg

Situation / Opgave Brug Web Components Brug et Framework (React, Vue, Angular)
Du bygger et internt designsystem (knapper, input-felter, menuer) til en hel virksomhed.✔ Ja✖ Nej
Virksomheden har flere forskellige web-apps bygget i forskellige teknologier (f.eks. noget i React, noget i WordPress).✔ Ja✖ Nej
Du bygger en stor, kompleks Single Page Application (SPA) med avanceret data-flow (f.eks. et dashboard eller et socialt medie).✖ Nej✔ Ja
Komponenterne skal kunne overleve i mange år uden teknisk vedligeholdelse og opdateringer.✔ Ja✖ Nej
Du har brug for avanceret state-management, komplekse formular-valideringer og routing (sideskift).✖ Nej✔ Ja

Hvornår skal du vælge Web Components?

1. Til tværgående designsystemer (UI-biblioteker)
Hvis du sidder i en mellemstor eller stor virksomhed, har I højst sandsynligt flere forskellige digitale produkter. Det ene team bygger måske i React, det andet i Vue, og marketingafdelingen bruger WordPress eller ren HTML.
Hvis du bygger virksomhedens officielle knapper, formularer og korthoveder som Web Components, kan alle teams bruge nøjagtig de samme komponenter. De skal bare indlæse én JavaScript-fil, og så virker jeres <firma-knap> overalt.

2. Når holdbarhed og uafhængighed er kritisk
Når du bygger noget i et framework, gifter du dig med den teknologi. Hvis du bygger et stort komponentbibliotek i React 18, og React 19 eller 20 ændrer spillereglerne (breaking changes), skal du bruge mange timer på at opdatere din kode. Web Components er indbygget i browseren. Kode, du skriver i dag, vil med 99% sikkerhed køre fuldstændig fejlfrit om 10 eller 15 år, uden at du har rørt en finger i mellemtiden.

Hvornår skal du vælge et Framework?

1. Til komplekse, datatunge applikationer
Hvis du skal bygge en applikation i stil med Spotify, Airbnb eller et avanceret administrations-dashboard, bliver Web Components hurtigt for tunge at danse med i rå form. Frameworks som React, Vue og Angular har fantastiske økosystemer til:

  • Avanceret State Management: At styre global data, der lynhurtigt skal synkroniseres på tværs af 50 forskellige komponenter på skærmen samtidigt.
  • Routing: At styre URL-adresser og lynhurtige sideskift uden at genindlæse browseren.
  • Økosystem: Adgang til millioner af færdige pakker (npm) til komplekse grafer, kalendere og drag-and-drop funktioner.

2. Når udviklingshastighed i store teams er vigtigst
Frameworks tvinger ofte alle udviklere til at skrive kode på den samme strukturerede måde. Det gør det nemt for nye programmører at hoppe ind i et projekt og forstå, hvordan data flyder. Med rå Web Components skal du i højere grad selv opfinde dybe tallerkner i forhold til applikationsarkitektur.

Den gyldne middelvej: De kan bruges sammen!

Det er vigtigt at huske, at det ikke er en "enten-eller"-krig. Du kan sagtens bruge Web Components inde i et React- eller Vue-projekt.
Mange moderne virksomheder vælger en hybrid-løsning: De bygger deres visuelle byggeklodser (knapper, input-felter, ikoner) som Web Components (ofte ved hjælp af letvægtsværktøjer som Lit), så designet er ensartet på tværs af hele koncernen. Derefter tager de app-specifikke teams disse Web Components og klistrer dem ind i deres React- eller Vue-apps, hvor de styrer den tunge forretningslogik.

Klar til at bygge dit eget UI-bibliotek?

Brug din nye viden om Web Components til at skabe lynhurtige, genanvendelige og fremtidssikrede brugerflader.