Hopp til innhold
Gjennomgang · BUILD10. juli 2026 · 7 min

Slik bygde jeg en guidet omvisning for en Astryx-app

Jeg trengte en guidet omvisning i en Astryx-app. Et generelt React-bibliotek løste mye, men to detaljer i integrasjonen ledet til en mindre løsning bygget rundt React-refs.

Av Ken Ove FerbuHamar · 10. juli 2026

Jeg trengte en guidet omvisning i FerbuTech Hub. Oppgaven virket ganske vanlig: marker en del av grensesnittet, forklar hva den gjør, og gå videre til neste steg.

Jeg begynte med et generelt React-bibliotek for produktomvisninger. Det var fortsatt det fornuftige stedet å starte. Biblioteket håndterte allerede plassering, tastaturstyring, fokus og markeringen rundt hvert mål.

Den første integrasjonen passet likevel ikke helt inn i appen. Siden ble mørkere, men informasjonskortet var borte. Da jeg hadde løst det, fant omvisningen bare noen få av elementene den skulle peke på.

Det betydde ikke at biblioteket var dårlig. Problemene kom fra to antakelser som ikke holdt gjennom komponentgrensene i denne Astryx-appen.

Arbeidet med disse grensene ble til Astryx Joyride (https://github.com/IgloooNOR/astryx-joyride), en liten pakke for guidede omvisninger bygget rundt React-refs.

Hva omvisningen måtte håndtere

Omvisningen går gjennom et Astryx AppShell. Den peker på dashbordet, navigasjonen, innstillinger, et administratorområde og knappen som lar brukeren starte omvisningen på nytt.

Ikke alle elementene finnes samtidig. Navigasjonen endrer seg mellom stor og liten skjerm. Noen områder vises bare til brukere med bestemte rettigheter. Andre dukker opp først etter at en rute eller datakilde er ferdig lastet.

Appen må også huske at brukeren har fullført omvisningen, uten å låse muligheten til å kjøre den igjen senere.

En nyttig løsning trengte derfor mer enn en rekke verktøytips. Den måtte forholde seg til ruter, responsiv navigasjon, tilgangsstyring, asynkron rendering, tastaturbruk og gjenoppretting av fokus.

Informasjonskortet havnet utenfor temaet

I den første versjonen ble appen dempet som forventet, men selve informasjonskortet var i praksis usynlig.

Biblioteket rendret kortet gjennom en React-portal under document.body. Det er en vanlig og rimelig standard for lag som skal ligge over resten av grensesnittet.

I denne appen var Astryx-temaet festet til en nøstet <Theme>-grense rundt selve applikasjonen. Portalen flyttet derfor kortet utenfor delen av DOM-treet der CSS-variablene fra Astryx var tilgjengelige.

Bakgrunnen kunne fortsatt tegnes. Kortet fant derimot ikke fargene, typografien, avstandene og de andre temaverdiene det forventet.

Jeg flyttet portalverten inn i det tematiserte applikasjonsskallet. Da kom kortet tilbake med riktige Astryx-komponenter og riktig visuell behandling.

Lærdommen er enkel: Hvis et lag er avhengig av verdier fra et designsystem, må portalen ligge innenfor området der verdiene faktisk finnes.

Selektorene nådde ikke DOM-elementet

Det neste problemet var mindre synlig.

Stegene brukte CSS-selektorer knyttet til data-*-attributter. Attributtene sto i React-koden, men noen av dem kom aldri frem til DOM-noden omvisningen forsøkte å måle.

Astryx bruker sammensatte komponenter som SideNavItem. Komponenten bestemmer hvilke props som skal ligge på roten, hvilke som skal videre til en intern lenke, og hvilke som ikke er en del av det offentlige API-et. En adapter for ruting kan legge til enda en grense.

Dermed kunne selektoren se riktig ut der komponenten ble brukt, samtidig som den manglet i den ferdig renderte HTML-en.

Biblioteket behandlet et manglende mål som et steg det kunne hoppe over. Når flere mål manglet etter hverandre, gikk omvisningen gjennom nesten hele listen og stoppet først ved det siste elementet den faktisk fant.

Jeg kunne ha flyttet attributtene og fått akkurat denne skjermen til å virke. Det ville ikke fjernet avhengigheten av at alle komponentlag videresender selektoren til riktig node.

Registrering med en React-ref

React gir oss allerede en direkte referanse til et rendret element. Astryx Joyride bruker denne referansen i stedet for å lete etter elementet på nytt med querySelector().

Et mål registrerer seg med useGuidedTourTarget():

const dashboardRef = useGuidedTourTarget<HTMLElement>('nav-dashboard');

<SideNavItem
  ref={dashboardRef}
  href="/dashboard"
  label="Dashboard"
/>

Steget bruker den samme ID-en fra registeret:

const steps: readonly GuidedTourStep[] = [
  {
    id: 'dashboard',
    targetId: 'nav-dashboard',
    title: 'Dashbordet ditt',
    content: 'Se arbeid som pågår og hvilke oppgaver som trenger oppmerksomhet.',
    placement: 'end',
  },
];

Provider og omvisning blir liggende inne i Astryx-temaet:

<Theme theme={neutralTheme} mode="system">
  <GuidedTourProvider>
    <App />
  </GuidedTourProvider>
</Theme>
<GuidedTour
  isOpen={isOpen}
  steps={steps}
  portalTargetId="app-shell"
  restoreFocusTargetId="tour-launcher"
  onOpenChange={setIsOpen}
/>

Registeret følger elementet når det monteres, forsvinner eller blir erstattet. Den aktive omvisningen abonnerer på endringene og kobler målingen til den noden som faktisk finnes nå.

Denne detaljen ble viktig i testingen. En oppdatering av skallet erstattet et navigasjonselement etter den første målingen. Den gamle noden var koblet fra og returnerte et tomt rektangel. Ved å lytte til ref-endringer kunne omvisningen koble seg til det nye elementet.

Manglende mål blir synlige

Et mål kan mangle av helt normale grunner. En rute kan fortsatt laste. Menyen på mobil kan være lukket. Brukeren har kanskje ikke rettighet til å se området.

Astryx Joyride venter når et mål fortsatt kan dukke opp. Hvis tidsgrensen går ut, blir omvisningen stående på samme steg og forklarer at området ikke er tilgjengelig.

Brukeren kan prøve igjen, gå tilbake eller hoppe over resten. Komponenten later ikke som om steget ble fullført.

Det gjør integrasjonsfeil lettere å oppdage. Samtidig gir det appen rom til å håndtere normal asynkron oppførsel uten at omvisningen mister deler av historien.

Det synlige laget bruker Astryx

Informasjonskortet bygges med Astryx-komponentene Card, Heading, Text, VStack, HStack og Button. Farger, radius og lagdeling kommer også fra temaverdiene i Astryx.

Pakken håndterer den gjenbrukbare oppførselen:

  • Tilbake, Neste, Hopp over, Fullfør og Prøv igjen
  • logisk plassering med start og end, inkludert støtte for høyre-til-venstre-språk
  • vending og begrensning mot kanten av skjermen
  • ny måling ved endret størrelse og nøstet scrolling
  • Escape og piltaster
  • fokus inne i informasjonskortet
  • gjenoppretting av fokus når omvisningen lukkes
  • språktilpassede etiketter
  • callbacks som varsler om fullføring og stegbytte

Appen beholder produktvalgene. Den bestemmer stegene, lagrer fullføring, sender analysehendelser og avgjør om den må navigere før neste mål kan vises.

Denne grensen holder pakken liten og hindrer at den lærer detaljer om ruting, autentisering eller lagring fra én bestemt app.

Fra intern kode til egen pakke

Den første fungerende versjonen lå inne i FerbuTech Hub og brukte norske etiketter.

For å gjøre løsningen til en egen pakke måtte jeg fjerne antakelser om appen, legge til engelske standardtekster og overstyrbare etiketter, skille ut geometrien og dokumentere et kontrollert API.

Pakken har også enhetstester, en demo med nøytralt tema, TypeScript-deklarasjoner og ferdig bygget ESM-kode.

Jeg la den bygde koden inn i GitHub-repoet fordi pakken foreløpig installeres direkte derfra. Da virker installasjonen også når pakkebehandleren blokkerer livssyklusskript i avhengigheter.

Den testede versjonen er:

bun add github:IgloooNOR/astryx-joyride#v0.1.1

Kildekode og dokumentasjon ligger her:

github.com/IgloooNOR/astryx-joyride (https://github.com/IgloooNOR/astryx-joyride)

Kan dette bli en del av Astryx?

Kanskje, men en komponent som fungerer i ett produkt er ikke automatisk klar for et designsystem.

Astryx har en prosess for forslag og spesifikasjon av nye komponenter. Den starter med problemet, dokumentasjon på at behovet finnes, og enighet om det offentlige API-et. Implementering og grundigere tilgjengelighetstesting kommer senere.

Det er fortsatt noen spørsmål som må avklares:

  • Bør komponenten hete GuidedTour, ProductTour eller noe basert på informasjonskort?
  • Bør målene ligge i et register hos provideren eller som refs direkte i hvert steg?
  • Skal aktivt steg kunne styres fullt ut av appen?
  • Kan eksisterende lag- og popover-primitiver i Astryx eie mer av oppførselen?
  • Trenger Astryx en gjenbrukbar komponent, eller er en dokumentert oppskrift nok?

Jeg har skrevet en vurdering for oppstrømsbidrag og et utkast til RFC, men ikke sendt dem inn. Den separate pakken kan først prøves i virkelige apper. Et eventuelt offisielt bidrag bør fortsatt følge prosessen til Astryx.

Det jeg ville tatt med videre

Tre valg har allerede vist seg nyttige:

  1. Hold temabaserte lag innenfor temagrensen.
  2. Registrer elementet du trenger i stedet for å anta at en selektor overlever alle komponentlag.
  3. Vis et manglende steg slik at bruker og utvikler kan velge hva som skal skje.

Det visuelle uttrykket og API-et kan fortsatt endre seg. Disse tre valgene løser integrasjonsproblemene som startet arbeidet, og er delene jeg ville brukt igjen i en annen app.