Sundsvall DS
Alternativ A. Designsystem för Sundsvalls kommuns digitala tjänster som tolkar profilen troget: utgår från de tolv färgerna, bygger på shadcn/ui och är skrivet så att både människor och AI-agenter kan bygga rätt. Den här sidan är hela systemet: varför det behövs, principer, grunder, komponenter, mönster, färdiga tillämpningar och nästa steg.
Byt verksamhetsfärg och läge uppe till höger. Allt på sidan ritas från samma tokens.
Grunder
Färgskalor, semantiska tokens, lager och elevation, radie, avstånd, typografi, ikoner och rörelse. Allt har ett namn och ett värde i tokens.json.
Komponenter
shadcn/ui oförändrat, tematiserat via tokens. Installeras från kommunens registry. Varje komponent har regler för när den används.
Mönster och tillämpningar
Åtta namngivna mönster som täcker de flesta kommunala tjänster, och fem hela vyer som visar hur de sätts ihop.
Systemets delar
| Del | Fil eller plats | Ägare | Läses av |
|---|---|---|---|
| Tokens | tokens.json (W3C Design Tokens) | Kommunikation + IT | Byggverktyg, Figma, AI |
| Tema | globals.css, genererad | IT | Tailwind, shadcn |
| Regler | AGENTS.md | Kommunikation + IT | AI-agenter, utvecklare, lint |
| Komponenter och mönster | design.sundsvall.se/r (shadcn-registry) | IT | npx shadcn add, MCP |
| Dokumentation | Den här sidan, genererad | Alla | Människor |
Varför blir dagens tjänster platta?
Den grafiska profilen är gjord för tryck och skyltar. Den har fyra mörka färger, fyra mycket ljusa och fyra grå. För en skärm saknas allt däremellan. Det är inte ett smakproblem utan ett strukturproblem, och det har tre orsaker.
Två steg per färg
Mellan mörk och ljus finns inget. Inga hover-, aktiv- eller inaktiv-lägen, inga mellantoner för diagram, ingen text som är "lite mindre viktig".
Fyra gråtoner
Ett gränssnitt behöver 10–12 neutrala steg för kanter, ytor, hjälptext och avaktiverade fält. Med fyra blir allt antingen vitt eller ljusgrått.
Ingen definition av yta
Profilen säger vilka färger som finns, inte vad som ligger på vad. Utan lager, skugga och kantregler blir kort, sida och dialog samma vita rektangel.
Fem principer
Profilfärgerna är ankare, inte hela paletten
Varje profilfärg bevaras exakt och blir ett fast steg i en elvastegsskala. Skalan är beräknad i OKLCH så att stegen upplevs jämna och nyansen inte driver.
En tjänst, en verksamhetsfärg
Grön, blå, lila eller rosa väljs en gång per tjänst. Allt annat är neutralt. Mättad färg upptar under tio procent av ytan och betyder alltid handling eller status.
Djup är information
Bakgrund, yta, upphöjd yta och dialog är fyra namngivna lager med varsin elevation. Radie följer samma hierarki. Det är detta som tar bort plattheten.
Text är sanningen, bilden är en vy
Systemet lever i tokens.json och AGENTS.md. CSS, Tailwind, Figma och dokumentation genereras därifrån. En AI som läser texten kan bygga rätt utan att ha sett en skiss.
Standard före specialitet
Komponenterna är shadcn/ui oförändrade där det går, eftersom varje AI-modell redan kan dem. Kommunens egna bidrag är tokens, regler och namngivna mönster, inte nya komponentbibliotek.
Färg
Sex skalor med elva steg. Rutor med ram är profilens exakta hex-värden. Övriga steg är beräknade i OKLCH från dessa ankare så att stegen upplevs jämna. Kontrast mot vitt är förberäknad per steg.
Ljus text på färg
Från steg 600 och uppåt (Vattjom 700, Björnstigen 700) klarar vit text 4.5:1.
Mjuka ytor
Steg 200 som bakgrund, steg 900 som text och steg 500 som kant. Kanten gör att ytan syns även på en TV som tappar ljusa toner.
Diagram
Alltid i ordningen chart-1…5. Fler än fem serier: gruppera eller dela upp.
#f3fcfe#daeff1profil#cce4ff#a6d1fe#7eb4ed#5595d5#327cc2#1264a9#005595profil#003a69#00274a#f3fef3#e0f6deprofil#bfefcc#9ddeaf#74c48d#48a76a#278e52#00733bprofil#045d2f#004621#003015#f9f9ff#f0eef9#d7dbf2profil#dbc0ec#c49edb#a97ac5#945db3#7f419f#692a88#5b1f78profil#3c0054#fff8f9#faecf0#fedfe2profil#feb1d8#f583c1#e153a6#cb298f#a90074profil#89055e#690047#4a0030#ffffffprofil#f8fafd#f1f4f6#ecececprofil#d2d6da#b4b8bc#919498#707377#4b4b4bprofil#3c3d3f#252728#151618#000000profil#fff9f2#ffedda#ffd8ad#fdbc6f#e9a03e#d0860f#b07004#915c06#734804#563502#3c2200info med ikon för varningar.Semantiska tokens
Komponenter använder aldrig en skala direkt. De använder roller. Namnen är shadcn/ui:s egna plus ett litet tillägg för ytor och status. Verksamhetsfärg väljs med data-brand på dokumentet; mörkt läge följer systemet.
| Token | Ljust | Mörkt | Tailwind-klass | Roll |
|---|---|---|---|---|
background | neutral-200 | neutral-950 | bg-background | Sidans grund |
card / surface | neutral-0 | neutral-900 | bg-card | Kort, paneler. Elevation 1. |
popover / surface-raised | neutral-0 | neutral-800 | bg-popover | Meny, dropdown, dialog. Elevation 2–3. |
surface-sunken | neutral-200 | neutral-950 | bg-surface-sunken | Sammanfattning, kod, inaktivt |
foreground | neutral-900 | neutral-100 | text-foreground | Text |
muted-foreground | neutral-700 | neutral-400 | text-muted-foreground | Hjälptext, metadata |
border / input | neutral-400 / 500 | neutral-700 / 600 | border-border / border-input | Avgränsning / fältkant |
primary | brand-ankare | brand-300 | bg-primary text-primary-foreground | Handling. En per vy. |
secondary | neutral-100 | neutral-800 | bg-secondary | Sekundär knapp |
accent / primary-soft | brand-200 + kant brand-500 | brand-950 | bg-accent text-accent-foreground | Vald rad, aktiv flik |
ring | brand-600 | brand-400 | ring-ring | Fokus |
success | grönsta-700 | grönsta-400 | bg-success-soft text-success-soft-foreground | Klart, beviljat |
info | vattjom-800 | vattjom-400 | bg-info-soft text-info-soft-foreground | Upplysning |
warning | varning-700 | varning-400 | bg-warning-soft text-warning-soft-foreground | Kräver uppmärksamhet |
destructive | juniskär-700 | juniskär-400 | bg-destructive text-destructive-foreground | Fel, ta bort, avslag |
chart-1…5 | vattjom-700, grönsta-600, björnstigen-600, juniskär-600, varning-500 | fill-chart-1 | Diagramserier i ordning | |
Djup, radie och avstånd
Fyra lager med varsin elevation. Skuggor tonade i profilens mörkblå. Radie och avstånd följer samma hierarki som lagren, så att storlek, höjd och rundning berättar samma sak.
surface · elevation 1Kort, formulärsektionsurface-raised · elevation 2Popover, dropdown, menysurface-sunkenSammanfattning, kod
backgroundElevation
| 0 | Rader, tabeller, flikar |
| 1 | Kort på bakgrund |
| 2 | Popover, hover på kort |
| 3 | Dialog, sheet, toast |
Radie
Chip och tabellkontroll → sm. Knapp och fält → md. Kort → lg. Dialog → xl.
Avstånd (4 px-skala)
Inom komponent 8–12. Mellan fält 16–24. Mellan sektioner 32–48. Sidmarginal 20 på mobil, 28–40 på desktop.
Typografi, ikoner och rörelse
Raleway för rubriker på nivå 1–2, Inter för allt gränssnitt, JetBrains Mono för ärendenummer och kod. Ikoner från Lucide (shadcn:s standard) i 20 px med 2 px linje. Rörelse bara som svar på en handling.
Regler för text
- Aldrig under 12 px, aldrig versaler.
- Brödtext max 70 tecken per rad.
- Rubriker i meningsform: "Vem gäller ansökan?"
- Knappar säger vad som händer.
Ikoner
- Lucide, 20 px i knappar och listor, 16 px i märken, 24 px i tomma vyer.
- Ikon före text, aldrig ensam utan
aria-label. - Statusikoner följer statusfärgen.
Rörelse
- 120 ms hover och fokus.
- 200 ms öppna och fälla ut.
- 320 ms dialog och sheet.
- Kurva 0.2, 0, 0, 1. Respekterar reduced-motion.
Komponenter
shadcn/ui:s komponenter ritade med kommunens tokens. Varje komponent har ett "använd när" och ett kodexempel som är den enda tillåtna formen. Installera med npx shadcn@latest add https://design.sundsvall.se/r/<namn>.json.
Button
r/button.jsonAnvänd när
- En primär per vy: den handling som för ärendet framåt.
- Sekundär för "spara utkast", outline för "avbryt".
- Ghost för navigering bakåt. Link inne i löptext.
- Destructive bara för oåterkalleliga handlingar, alltid med bekräftelse.
<Button>Skicka in ansökan</Button>
<Button variant="secondary">Spara utkast</Button>
<Button variant="outline">Avbryt</Button>Input, Select, Textarea, Label
r/input.json · r/select.json · r/textarea.json · r/label.jsonRegler
- Alltid synlig label ovanför. Placeholder ersätter aldrig label.
- Hjälptext under fältet, före felet uppstår.
- Fel: kant i destructive, meddelande vid fältet med vad som är fel och hur man rättar det.
- Höjd 40 px, 48 på mobil. Bredd efter förväntat innehåll (personnummer ≠ adress).
<Label htmlFor="pnr">Personnummer</Label>
<Input id="pnr" inputMode="numeric"
aria-describedby="pnr-help" />
<p id="pnr-help" className="text-sm text-muted-foreground">
12 siffror, med eller utan bindestreck
</p>Checkbox, Radio, Switch
r/checkbox.json · r/radio-group.json · r/switch.jsonAnvänd när
- Checkbox: flera val eller ett godkännande.
- Radio: ett val av 2–5 alternativ som alla ska synas.
- Fler än fem alternativ: Select.
- Switch: inställning som slår igenom direkt, aldrig i ett formulär som skickas.
Alert (notice)
r/alert.jsonRegler
- Mjuk bakgrund, mörk text. Mättad färg bara på ikon och kant.
- Alltid ikon och rubrik; färg är aldrig ensam bärare.
- Fel säger vad som hände och vad man gör.
- Max ett meddelande per sektion. Fler: en sammanfattning överst.
<Alert variant="info">
<Info />
<AlertTitle>Du behöver e-legitimation</AlertTitle>
<AlertDescription>Logga in med BankID …</AlertDescription>
</Alert>Badge
r/badge.jsonRegler
- Bara för status, aldrig som dekoration eller kategori.
- Ett ord eller två. Prick före text för färgblinda.
- Ärendestatus har fasta namn: Utkast, Mottaget, Handläggs, Komplettering behövs, Beviljat, Avslag, Avslutat.
Card
r/card.jsonSkolskjuts, Elias
HandläggsSBK-2026-04471 · Skickad 14 september
Förskoleplats
Ställ ditt barn i kö och följ din plats i kön.
Regler
- Kort ligger på bakgrund, aldrig på kort.
- Elevation 1; hela kortet klickbart om det har en handling, annars en knapp.
- Radie lg, padding 20.
- Lista med fler än sex kort: överväg tabell.
Tabs, Breadcrumb, Pagination
r/tabs.json · r/breadcrumb.json · r/pagination.jsonRegler
- Tabs för vyer av samma sak, max fem. Aldrig för formulärsteg – använd step-form.
- Breadcrumb på alla sidor under startnivån. Sista nivån utan länk.
- Pagination vid fler än 25 rader; visa antal totalt.
Table
r/table.json · r/data-table (mönster)Regler
- Elevation 0, rader skiljs med border. Hover: surface-sunken. Vald: primary-soft.
- Max sju kolumner synliga; övriga i raddetalj.
- Tal högerställda, datum korta, ID i mono.
- Scrollar horisontellt i egen behållare på mobil, aldrig sidan.
Dialog och Toast
r/dialog.json · r/sonner.jsonTa bort utkastet?
Utkastet "Skolskjuts, Elias" tas bort permanent. Det går inte att ångra.
Regler
- Dialog bara för beslut som inte kan tas i sidan. Rubrik som fråga, knappar som upprepar handlingen.
- Avbryt-knappen beskriver vad som händer: "Behåll utkastet", inte "Avbryt".
- Toast för bekräftelse av det man just gjorde, 5 s, med Ångra om möjligt. Aldrig för fel som kräver handling.
Skeleton och laddning
r/skeleton.jsonRegler
- Skeleton i samma form som innehållet, vid laddning över 300 ms.
- Knapp som arbetar: inaktiv med spinner och samma text.
- Aldrig helsidesspinner i en e-tjänst.
Mönster
Åtta namngivna block som beskriver en vy innan koden skrivs. Både människor och AI-agenter refererar till dessa namn. De installeras från registryt som färdiga kompositioner av komponenterna ovan.
service-header
r/service-header.jsonAnsök om skolskjuts
Skolskjuts kan beviljas om vägen till skolan är längre än fyra kilometer eller trafikfarlig. Ansökan tar ungefär tio minuter.
Innehåll i ordning
- Breadcrumb och verksamhetsområde.
- Rubrik som verb: "Ansök om …", "Anmäl …".
- Två meningar: vem, vad, hur lång tid.
- Fakta: inloggning, handläggningstid, kostnad.
- En primärknapp och en länk till regler.
step-form
r/step-form.jsonRegler
- 3–6 steg, varje steg en fråga. Stegindikator med "Steg n av m" som text.
- Ett kort per steg på bakgrunden. Sparas automatiskt.
- Knappar alltid i samma ordning: Tillbaka vänster, spara och fortsätt höger.
- Sista steget är alltid en summary-card.
case-status
r/case-status.jsonSkolskjuts, Elias
Handläggs- Mottagen14 september
- HandläggsSedan 15 september
- BeslutSenast 28 september
Regler
- Tidslinje med fasta steg per ärendetyp. Klart = success, pågår = primary, kommande = border.
- Alltid ett datum eller ett "senast"-datum per steg.
- Komplettering visas som warning-notice ovanför tidslinjen med en knapp.
summary-card
r/summary-card.json- Namn
- Elias Lund
- Personnummer
- 20160223-4412
- Skola
- Bergsåkers skola, årskurs 4
Regler
- Nedsänkt yta, en sektion per formulärsteg, "Ändra"-länk som hoppar tillbaka.
- Föregås alltid av personal-data-notice och följs av primärknappen "Skicka in ansökan".
personal-data-notice, notice, empty-state, data-table
r/personal-data-notice.json · r/notice.json · r/empty-state.json · r/data-table.jsonDu har inga pågående ärenden
När du skickar in en ansökan eller anmälan visas den här, så att du kan följa den.
Regler
- personal-data-notice: standardtext, alltid före inskick, info-variant med lås-ikon.
- notice: Alert med regler enligt komponenten.
- empty-state: ikon, rubrik som konstaterar, en mening som förklarar, en primärknapp.
- data-table: Table med sortering, filter, radval och pagination. Tom tabell visar empty-state.
Tillämpningar
Fem hela vyer byggda enbart av komponenterna och mönstren ovan. Varje vy anger vilka mönster den använder. Byt verksamhet uppe till höger för att se samma vy i en annan förvaltning.
1. E-tjänst: sista steget innan inskick
Mönster: service-header (kort), step-form, summary-card, personal-data-notice, case-status.
Ansök om skolskjuts
Steg 4 av 4: Granska och skicka in.
Granska dina uppgifter
- Namn
- Elias Lund
- Personnummer
- 20160223-4412
- Skola
- Bergsåkers skola, årskurs 4
- Adress
- Kvarnvägen 12, Sundsbruk
- Avstånd
- 5,2 km
- E-post
- anna.lund@exempel.se
- Telefon
- 070-123 45 67
2. Mina ärenden: översikt för medborgaren
Mönster: data-table, case-status (kompakt), notice. Sidonavigation med accent för aktiv sida.
Mina ärenden
| Ärende | Gäller | Status | Uppdaterad | |
|---|---|---|---|---|
| BYG-2026-01188 | Bygglov, altan | Komplettering behövs | 11 sep | |
| SBK-2026-04471 | Skolskjuts, Elias Lund | Handläggs | 15 sep | |
| FEL-2026-22910 | Felanmälan, gatubelysning | Mottaget | 9 sep |
3. Handläggarvy: statistik och diagram
Intern tjänst byggd på samma tokens som medborgarvyerna. Diagram använder chart-1…5 i ordning. Stat-kort på elevation 1.
Skolskjuts, läsår 2026/2027
Inkomna per vecka
Toppen v.35 följer skolstarten. Vecka 38 pågår.
Beslut per grund
4. Mobil: bekräftelse efter inskick
Knappar 48 px, en kolumn, samma tokens. Mönster: notice (success), case-status.
- Ärendenummer
- SBK-2026-04471
- Mottagen16 september
- Handläggs
- BeslutSenast 30 september
5. Tom vy och bekräftelsedialog
Mönster: empty-state. Dialog på elevation 3 med knappar som upprepar handlingen.
Inga fakturor ännu
Fakturor för förskola, fritids och musikskola visas här när de skapas.
Skicka in utan bilaga?
Du har inte lagt till något intyg. Ansökan kan handläggas ändå, men handläggaren kan komma att be om komplettering.
Kontrast på TV, projektor och i dagsljus
Ljusa toner försvinner först: en TV i butiksläge, en projektor i ett ljust rum eller en mobil i solen klipper bort allt under ungefär 1,3:1. Systemet är därför byggt så att ingen information bärs av en ljus ton ensam, och det har ett högkontrastläge för de miljöerna.
Grundnivån (gäller alltid)
- Bakgrund mot kort: 1,18:1 (profilgrå #ececec mot vitt), och varje kort har en kant på 2:1.
- Mjuka statusytor: bakgrund steg 200, text steg 900, kant steg 500 (≥ 3:1) och ikon. Tonen får försvinna, kanten och ikonen finns kvar.
- Fältkanter 3:1, hjälptext 9,7:1, skuggor bär alltid en verklig kant.
Högkontrastläget
- Slås på med
data-contrast="high"eller automatiskt avprefers-contrast: more. - Bakgrund 1,46:1, kanter 4,8:1, statusytor steg 300 med kant steg 700, skuggor ersätts av kanter, inget glas.
- Testa med knappen "Simulera TV" uppe till höger: den efterliknar en skärm som tappar ljusa toner. Allt som är läsbart där är läsbart på TV.
Kort på bakgrund
Kanten är alltid där; skuggan är en bonus.
- Nedsänkt yta
- Steg 200 med kant
Gör och gör inte
Blicken hittar direkt vad som för ärendet framåt.
Allt är lika viktigt, alltså inget.
Läsbar text, färg som förstärker.
Beslutet skickas till din digitala brevlåda.
Tung, skriker, och ser ut som en knapp.
Bygglov
BYG-2026-01188
Ett lager, en elevation.
Skuggor staplas, kanter fördubblas, djupet blir brus.
Vad som är fel och hur det rättas.
Vilket fält? Vad är fel? Användaren gissar.
Arbeta med AI
Systemet är byggt för att en agent ska kunna bygga en hel tjänst utan att ha sett en skiss. Så här ser arbetsflödet ut, och så här ser en bra beställning ut.
npx shadcn add …/r/step-form.json och komponera. Enbart semantiska tokens.Exempel på beställning till agenten
Det som gör beställningen bra: mönsternamn i stället för utseende, verksamhetsfärg angiven, innehållet (steg, krav, tider) specificerat, och ett krav på att verifiera.
Vad agenten får och inte får
| Får | Komponera mönster, skriva klarspråk, välja fältordning, lägga till hjälptext, bygga nya vyer av befintliga block. |
| Får inte | Skriva färgvärden, skapa nya komponenter, blanda verksamhetsfärger, använda tabs för steg, lägga mer än en primärknapp per vy. |
| Måste fråga | När ett behov inte täcks av de åtta mönstren. Svaret blir ett nytt mönster i registryt, inte ett undantag i tjänsten. |
MCP
Registryt exponeras via shadcn:s MCP-server, så att agenter kan slå upp komponenter, mönster och tokens som verktyg i stället för att läsa dokumentation. Samma källa, tre läsare: människa, byggverktyg, agent.
Nästa steg
- Beslut om två frågorVarningsfärgen (tillägg utanför profilen) och om Juniskär räcker som felfärg. Båda är markerade som öppna i AGENTS.md.
- Publicera registrytSätt upp
design.sundsvall.se/rmed de tolv shadcn-komponenter som används mest, plus de åtta mönstren. Tokens och globals.css finns redan. - Bygg en tjänst med enbart AIGe Claude Code AGENTS.md och registryt och låt den bygga om en befintlig e-tjänst. Det som går fel är det som saknas i regelboken.
- Lint i CIStylelint och eslint-regler enligt §1 och §8 i AGENTS.md. Utan detta är reglerna rekommendationer.
- Generera Figma sistFigma-variabler skapas från tokens.json, inte tvärtom. Designers arbetar i samma sanning som agenterna.