* 🎉 Selected options are now persistent in dropdown for single-select combobox
* 🎉 Persistent rendering of selected single item when possible
* Update @navikt/core/react/src/form/combobox/FilteredOptions/FilteredOptions.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 🐛 Handle overflow more gracefully
* ⚡ Optimize re-renders
* ⚡ Optimize timeline axis calculation
* 🐛 Fix overflow and sorting
* 🐛 Handle numer infinity issues
* 🐛 Fix case where left:0 returned null for period
* 🎉 Rewrite tabs to not use descendants
* 📝 changeset
* 🎉 Added tests
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* 📝 Rename to rovingFocus
* 🔥 Removed fallback to activeElement, handle no current
* [ToggleGroup] Refactored to not use descendants API (#4932)
* 🎉 Updated ToggleGroup to not use descendants API
* ♻️ Update rovingFocus and fallback
* [ActionMenu] Refactored to not use descendants API (#4933)
* 🎉 Updated ActionMenu to not use descendants API
* ♻️ Cleanup after roving rename
* 🐛 Update event-handler for close-submenu listener
* Update @navikt/core/react/src/utils/components/floating-menu/Menu.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Added small prop
* Added disableItemIndent prop
* Changes in group label styling, medium size styling and itenm indent
* Examples
* Changeset
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* make indent positive again
* remove hstack
* Move size to root so it can be added to context and applied to submenus too
* Add indent to SubMenu trigger too
* moved size prop
* Changed logic to indent being opt-in
* Correct story to new indent logic
* fix website examples based on changes
* Rename and fixed some spelling
* changeset
* add cromatic
* Update .changeset/great-kings-poke.md
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* ExpandableRow: Fix selected row top border not showing when row above was collapsed
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* more spesific css selector
* Wrote wrong classname 🤦♀️
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
[DataTable] Refactor loading state and body rendring.
[DataTable] Added emptyState prop
[DataTable] Removed render-prop for custom filterMenu
[DataTable] Updated stickyColumns jsdoc
[DataTable] Added loading states
[DataTable] Added loading label, better handling of colspan in loading/empty-state
* 🎉 Added 'isActive'-prop to InternalHeaderButton.
* 🐛 Use actual links in story view
* review updates: aria-current, forced-colors and taller line
* 🐛 Allow user to override aria-current
* 🐛 Fixed issue where using fixedWeeks still caused layout shifts in cases of last/first month
* 📝 storybook demo
* 💄 Now supoorts breakpoints better
* ♻️ Use css-variables
* 🐛 Even more placeholder space for chromatic demo
* 🐛 Accound for missing padding
* 📝 Moved story order
🎉 Add row/col span checks
🎉 Set up test
🚚 Extract utils to helpers
🎉 Observe active cell changes
🎉 Check for disabled elements when focusing
🐛 Handle selects
🎉 Handle inputs in table with keyboard nav
🎉 Inputs in table demo
⚡ Optimize lookup
* feat: Enhance GlobalSearchButton with platform-specific keyboard shortcuts
* feat: Update GlobalSearchButton to display platform-specific keyboard shortcuts
* Legg til "⌘" som default, legg til en liten animasjon som easer inn hurtigtasten ved førstegangs render
* 🧪 Use 24-alpine
* 🧪 Test auto-instrumentation
* 🎉 Fix produktbloggen cache
* 🎉 Test cache-handler in memory
* 🎉 Test custom cache handler with ttl
* 📝 Add cache handler and observability
* Add functionality for right placed icon
* add aksel website example
* remomve left padding for right icon
* add support for right icon on sub menu item
* changeset
* remove @property
* remove examples
* remove example
* remove example
* remove class and use data attr
* only add data attr if icon
* add story for icon position
* seperate the examples
* testing action menu header
* revert to old example
* let focus on container
* added example for actionMenu
* remove testing stuff
* changeset
* remove comments
* changeset
* New component for buttons and loader within Item
* New component from file name with icon
* Removed loader and error icon for file icon
* Use new components for new design
* Css styling for new design to Item
* Delete unused filed, replaced by ItemHeader
* changeset
* try to fix cycle dependency
* Fill width
* Remove min width and height
* 🔥 Removed changeset
---------
Co-authored-by: Anniken Granberg <anniken.granberg@na.no>
Co-authored-by: Anniken Granberg <43889032+nixenixe@users.noreply.github.com>
* change format description to norwegian
* Update date format descriptions to Norwegian standard
---------
Co-authored-by: Anniken Granberg <anniken.granberg@na.no>
* Added alignContent prop to GlobalAlertRoot
* test
* test
* test
* changeset
* Change to use data-attributes. Change prop name to "centered" and made it boolean.
* Added styling to darkside
* Changed with prop change to boolean
* Change css to use data-attributes
* correct changeset
* Update .changeset/warm-meteors-go.md
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
---------
Co-authored-by: Anniken Granberg <anniken.granberg@na.no>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* 🎉 Support print in Checkbox and Radio CSS
* 🔥 removed unused css
* 🧪 Chromatic print stories
* 🧪 Use outline for radio outline
* 🐛 Add print-color exact
* 🔥 Revert print-exact change
* 🐛 Show selected radio in readonly-mode + print
* ♻️ Extract leagcy-tests for popover
* 📝 rename main popover story
* ♻️ Extract leagcy-tests for tooltip
* 📝 Rename tooltip story
* ♻️ Remove extra async-assertions from waitfor
* 🎉 Fix prefixing of bg and border-tokens
* 🐛 Check that tokens is not already migrated to a valid value
* 📝 Changeset
* 🐛 Now correctly only checks opening/root element instead of all nested elements
* 📝 rename existingRepalcements
♻️ Use darkside-config where possible
🚚 Update website with darkside -> legacy migration
🚚 Update examples use of leagcy components
🔥 Remove legacy css support from website storybook
* 🧪 set up views for Process line
* 🐛 Handle connectorplacement for dot-version
* 📝 Docs for use of connectorPlacement
* ♻️ Implement new connector prop for legacy
* 📝 Changeset
* 📝 Changeset edit
* 🐛 Use border-image unset instead of none to help with lightningcss minification
* Update .changeset/mean-insects-lick.md
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Update aksel.nav.no/website/pages/eksempler/process/connector.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 📝 Add connector-stories to chromatic
* 🎉 Update Process-prop from connectorPlacement -> isTruncated
* 📝 Update website docs for new isTruncated prop in Process
* Update aksel.nav.no/website/pages/eksempler/process/truncated.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
These FNR might not be valid in the first place (correct checksum), but they look like
they could be valid, rather than check each one, it's faster to
simply overwrite each one with an invalid day or month.
Why could a currently unused FNR suddenly become real? eg a valid FNR
(working checksum) set in the future _could_ end up being assigned to
a real future unborn person, and even a senior citizen could become
a norwegian citizen and even use a valid FNR from the past. Best to
avoid them entirely.
It's not a big probability, but better safe than sorry.
setting the month to 99 makes them so they would never be issued (similar to what Dolly
does).
* 🎉 Check for deps in 'useClientLayoutEffect'
* ♻️ Update dependency arrays
* 📝 changeset
* 🏷️ Remove on mount effect from depending on react Dispatch functioN
* 🔥 No need to log that users without loging creds are missing token
* ⚡ Sharded playwright workflow
* 🐛 Add permissions
* 🐛 Run yarn install before playwright shards
* 🐛 Install yarn before running playwright
* 🐛 Sync playwright versions
* 🐛 Add webserver config to playwright run
* 🐛 Test changing service-container name
* 🐛 Try running service-container with env-variables
* 🧪 Try removing upgrade request
* 🧪 Try using image-name in url
* 🔥 Removed temp tests
* 🐛 Try hardcoding testapp path
* 🐛 Page goto relative path
* 🔥 Removed node-setup in merge
* 🎉 Fixed blog-upload, added mobile e2e
* 🐛 Test different base-path
* 🐛 Use service-container name
* 🐛 remove name prefix
* 🐛 Try removing http
* 🐛 Try using https
* 🐛 try re-adding tokens
* 🐛 Try hardcoding path
* 🐛 Rename to testapp
* 🎉 Remove e2e test shortening
* ⚡ Test increasing workers to 2 in CI
* ♻️ Clean up old e2e scripts
* 🐛 Use dev-server when testing locally
* ♿ Correct headlig-levels for component intros
* 🔥 Removed legacy exclusion
* 🔥 No need to checkout repo on run
* 🧪 Test for mobile webkit instead of mobile chrome
* 🎉 Sync global search based on query
* ♻️ Handle open/close state better
* 🎉 Properly update input state
* ♻️ Extract param logic to utility-hook
* ⚡ simplify form select
* 🔥 removed redundant comment
* ⚡ Removed redundant memo
* ⚡ Moved search to suspense
* 🎉 Suspense is now limited to within search, allowing button to render on mount
* ♻️ clean up global search views
* 🐛 Reset search on closebutton click
* ⚡ Make sure to clean up debounce call on unMount
* 🔥 Removed unnecesarry ternary
* 🎉 Run only local stories, not node_modules
* 🚚 Move test-config to separate dir
* 🎉 Added UI-tests to CI action
* 🐛 Fix test-command used in action
* 📝 rename action
* 🐛 Add storybook command with CI-flag
* 🧪 Add forced file paralellism
* ⚡ only test play-fn tests
* 🔥 Remove a11y annotation
* 🔥 Cleanup after fixing performance
* 🐛 Allow test-runner widget to run without crash
* Update .storybook/main.ts
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 📝 Name update
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Add 'kebab' synonym to ellipsis icons
* Delete .changeset/long-cameras-sing.md
No need for changeset for internal doc changes
---------
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* ✨ Adds FormSummary footer component
Adds a `FormSummaryFooter` component to the `FormSummary`.
Moves the `EditLink` from the header to the new footer for improved structure and consistency.
* 🚨 Adds usage warning for incorrect component placement
Adds a warning to prevent incorrect placement of a component within another.
This improves DX by guiding developers to use the intended structure for form summary components.
* ✨ Adds edit link to FormSummaryHeader
Adds an edit link to the FormSummaryHeader component during testing.
This allows users to easily navigate to the corresponding form section for editing.
Removes requirement for FormSummaryHeader to include EditLink - now optional
* ✨ Moves EditLink to FormSummary.Footer
Moves `EditLink` from `FormSummary.Header` to `FormSummary.Footer` for better component structure and to resolve design issues.
Adds `FormSummaryFooter` component and styles to support the `EditLink`.
Implements `useWarnIfContainsAny` hook to provide development-only warnings for incorrect usage of `FormSummary` subcomponents.
* 🏗️ Refactors FormSummary structure and adds slot warnings
Refactors the FormSummary component to improve structure and provide better guidance for developers.
- Introduces `FormSummary.Header` and `FormSummary.Footer` components as dedicated slots, enhancing the component's API.
- Implements `useForbidSlot` and `useRequireSlot` hooks to provide development-only warnings when components are misused within the wrong FormSummary slots, improving component usability and preventing common integration issues.
- Removes the previous complex logic for warnings within `FormSummaryHeader` in favor of the new slot-based approach.
✅ Renames usageWarnings to slotWarningProvider
🚚 Moves and renames `usageWarnings.tsx` to `slotWarningProvider.tsx` for clarity.
This change improves the naming and location of the slot warning utilities,
making their purpose more explicit and easier to discover.
It also enhances the documentation and debug tooling.
* ♻️ Updated API for slotwarning
* 📝 Added sb-story for testing
* 💄 Remove extra border-radius in footer
* 📝 Updated examples
* 📝 Added changeset
* ♻️ PR adjustments
* Apply suggestion from @HalvorHaugan
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 🎉 Added pencil-icon
* 📝 Updated jsdoc
* 📝 New changeset
* Apply suggestion from @HalvorHaugan
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Apply suggestion from @HalvorHaugan
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 🔥 Removed forced-debug option
* Apply suggestion from @HalvorHaugan
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Apply suggestion from @HalvorHaugan
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Apply suggestion from @HalvorHaugan
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Apply suggestion from @HalvorHaugan
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 📝 show hidden warning-messages
* 📝 Scope edit-link css
* 🧪 Fixed tests
* 📝 Sync EditLink updates in templates
* 🐛 Update last missing doc-change
* ♿ Responsive padding
---------
Co-authored-by: Ken <ken.aleksander@gmail.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* AvatarStack component
* storybook: preview with light/dark toggle
* avoid import cycle
* cards story++
* Avatar support empty data
* blogg: use AvatarStack and editorialStaff
* utils fn for blogg avatar
* 🚚 queryToAvatars
* 🔥 queryToAvatars
* snappier loading of Image
* refactor Component API (composeable)
* typing++
* update to new Avatar components
* 🚚 Avatar
* HoverCard (as styled Popover + button)
* SR redundant output
* remove stale comment
* Update Avatar.tsx
* HoverCard: use floating-ui instead of Popover
* HoverCard: use floating-ui instead of Popover
* update path for Avatar
* HoverCard css
* HoverCard: border-color
* missing old token for popover z index on website
* typing for utils
* HoverCardGroup
* use HoverCardGroup
* focus-visible
* p inside button is invalid html
* sync clientConfigs
* re-add the local datasett override
* [sanity-schema] team/miljø for Redaksjon
* redaksjon-som-redaktor] 🎨 render for new schema
* 🐛 broken storybook for aksel (tailwinds related)
* new required type param to stories
* remove HoverCard for now (enable when teams have descriptions)
* change schema for aksel_artikkel to use editorial_staff
* use new schema changes
* [website] use Avatar with editorial_staff for cards on frontpage
* ♻️ HighlightedArticle
* 💄 move date to top for blog entries
* typing++
* match design++
* typing++
* Update aksel.nav.no/website/app/(routes)/(produktbloggen)/produktbloggen/_ui/BloggList.tsx
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* Update aksel.nav.no/website/app/(routes)/(produktbloggen)/produktbloggen/_ui/BloggList.tsx
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* Update aksel.nav.no/website/app/(routes)/(produktbloggen)/produktbloggen/_ui/HighlightedBlogg.tsx
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* ♻️ move mapping utility fn to the resultant map location
* 🚚 from /dev/ -> not dev
* ♻️ avoid using fallback || ""
* stop using humanizeText + remove it, warn instead
* remove CSS that will be handled later, technically not used
* better a11y for reading out Avatar faster
---------
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* [Process] Init Process with clone of Stepper
* [Process] Add variants, state handling and custom overrides
* Clean up css and improve storybook
* [Process] Add component-level icon-overrides
* [Process] Make title optional and remove hideLine
* [Process] Align styles with design
* [Process] Fix high-contrast/forced-colors:active styling
* [Process] Clean up todos
* [Process] Clean up todos
* [Process] Fix lint errors
* [Process] Remove unused prop in Process-example
* [Process] Remove completedIcon and uncompletedIcon
* [Process] Simplify API by removing number and variant props on Step
* [Process] Fix storybook color role
* [Process] Fix styling of date and description
* [Process] Fix legacy css build by removing nesting
* [Process] Clean up jsdoc
* [Process] Update example
* [Process] Make dates in storybook dynamic
* [Process] Remove storybook example with Process-level icon-overrides
* [Process] Re-order storybook icon mix examples
* [Process] Adjust circle size to match design and switch from rem to px
* [Process] Fix according to comments
* [Process] Update Process example to not use 'description'-prop
* [Process] Support hiding of completed steps, with stepwise overrides
* [Process] Fix number size and content alignment
* [Process] Make default icon font-size responsive
* [Process] Remove support for deprecated tokens
* [Process] Adjust title alignment, make activeStep optional, update story
* [Process] Adjust title alignment
* [Process] Make circle numbers semibold and icon size dep on circle size
* [Process] Suggest examples
* ♻️ Collect process component into single file
* 📝 Revert story-name to avoid git naming conflict
* ♻️ Extract Checkbox icon
* ♻️ Make 'activeStep' optional
* ♻️ Update active API
* ♻️ Add line variant prop
* ♻️ Add 'endless' prop to Process
* ♻️ Make active item have colored line
* 💄 Remove 'current' step styling
* ♻️ Remove gap between process steps
* 💄 Connected lines to bullet
* ♻️ Icon -> bullet
* 🎉 Story for interactive content
* ♻️ remove completed prop, remove hideContent prop
* ♻️ Update naming and css syntax
* 🏷️ Make item -> step and step -> item
* 📝 Added demo with lineVariants
* 💄 Small transition on bullet-color
* 💄 Gradient border for current active step
* 💄 Update gradient stops
* 🔥 Removed lineVariant and reverActiveDirection props
* 🎉 Added a two-way aria-controls connection
* 🎉 Added checkmark sub-component
* 🏷️ Export Checkmark sub-component
* 🎉 Added nesting-support to legacy bundle build
* 🎉 Added back legacy css support + forced-colors support
* 🧪 Added CSS-validation for built CSS
* 📝 Changeset
* 📝 Eksempler
* 📝 Example adjustments
* 📝 Added checkmark and number-story
* ♿ Added role=list to ol-element
* Update @navikt/core/react/src/process/Process.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Update @navikt/core/react/src/process/Process.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Update @navikt/core/react/src/process/Process.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Update @navikt/core/react/src/process/Process.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* ⚡ Default to undefined childId
* 🔥 remove unused prop
* 🔥 Removed redundant CSS and props
* 🔥 removed forwardRef from utility components
* 🚚 Rename step -> event
* 💄 Testing active-state for process
* ♻️ Update gradient
* 💄 Sync process API with Figma
* ♻️ Removed context and mapping, moved control to event
* 💄 Added active-label styling
* 💄 Make sure active-label has correct spacing
* 📝 Added translations to Process label
* 🚚 Moved hideStatusLabel to Process, renamed translation
* 📝 Updated storybook with translations demo
* 📝 WIP examples update
* 📝 Synced examples with updated API
* 🎉 Added aria-controls sync across Process and Process.Event
* 🏷️ Add number type to function for ts
* 📝 Edit texts in example
* ⚡ Removed redundant memoization
* 📝 Removed date from future 'state' in process
* 📝 Update stories to match new format
* 📝 Renamed active translation, hideStatusLabel -> hideStatusText
* 📝 Removed heading labeling in examples
---------
Co-authored-by: Ken <ken.aleksander@gmail.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 🎨 hoist state up to searchForm, useEffect on handleSearch
* 🎨 clear textInput on semver checkbox
* perhaps feature complete endringslogg form
* Refactor
* use useOptimistic and use less useEffect
* dynamic default year
* clear other filters when toggling semver search
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 📝 WIP setup for semver search in changelog entries
* settings display & opt-out
* maxWidth to not break mobile
* make feature opt-in
* now support semver range searches
* join filter back to valid semver range syntax
* tweaks to search syntaks Popover and button
* use Helptext instead of custom Helptext
* checkbox for search preference
* default checked for snappy prefilled value from search params
* group all form into SearchForm for use client shennanigans and state management
* typing++
* ♻️ refactor state management for FilterChips
Why? Since the form is a bit more "complex" in hiding fields based on
other fields, it felt apropriate to "somewhat centralize" the state,
not going full context here, but sending down the useState() is fine
for now in terms of complexity imo.
Unfortunately the smarter / optimized behaviour has been disabled for
now, so no prefetching of the resulting query when hovering over a
FilterChip. It also requires you to hit enter or click the search icon
in order to trigger a search. It's more manual, but simpler to
understand (and to develop). Consider re-adding smarts. The smarts was
also not "fully smart" in its previous incarnation, since we couldn't
have the search be "instant and automatic" (not requiring any
enter/clicks beyond typing).
* typing
---------
Co-authored-by: Julian Nymark <julian.nymark@nav.no>
* [Stepper] Increase circle size to match Process component
* [Stepper] Fix circle size issue in legacy css
* [Stepper] Make completed checkmark font-size responsive
* [Stepper] Adjust completed-icon to 24x24 while still dynamic
* [Stepper] Add changeset
* [Stepper] Adjust title alignment
* Theme switcher exposed both icon's accessible name, even if only one icon was shown. This change properly removes the icon.
* fix hydration error and use resolvedTheme instead of theme
* ♻️ Use tooltip for theme-switcher button
* 🎉 Replace sun/moon icon with theme-icon
---------
Co-authored-by: Halvor Haugan <halvor.haugan@nav.no>
Co-authored-by: Ken <ken.aleksander@gmail.com>
* 🎉 Extract JSX for examples
* ✨ Added new jsx-data to examples-code
* 🏷️ Synced sanity-types
* 🐛 Make sure to remove trailing semicolon
* 🎉 Show compact code for examples and templates
* 📝 Adjust texts for expanding collapsed code-views
* 🎉 Add support for implicit return examples
* 🐛 Make sure to ignore nested return-statements
* 📝 Updates to expand/collapse texts in code-examples
* 🔥 Removed redundant boolean flag
* ♿ Chat ol-tag now has role presentation
* 🔥 Removed now redundant ol>li syntax for chat list
* 🔥 Removed now redundant list-reset css in chat
* 🔥 Removed now redundant list-reset css in chat in darkside
* 📝 Changeset
* 📝 Update changeset
* Delete .changeset/new-gifts-add.md
* Add designsystem front page skeleton
* Add heading and ingress
* Add getting started with design/development link cards
* Implement first working version of "layers" component
* Add an initial attempt at hover styling for ds layers component
* Refactor DS Landing page into separate components
* Add a change log section to ds landing page
* Add AkselByNumbers module
* Get real change log entries from Sanity
* Multiple changes made by pair-design-gramming with Sjur
* Updated sanity schemes
* Add missing contrast colors/variants for link and the typography components
* Use data-color for AkselByNumbers
* Use data-color and slightly tweak padding for PromoTag
* Re-add arrow to ChangeLogNews LinkCard
* Tweaks to LinkCard, using more square icon backgrounds (compared to round backgrounds before) with large border radius, and changing the icon color to subtle
* Per discussion with Ken, removing new contrast variant/textColor option, so as not to break old version of components. Replacing it with CSS for now, but may re-add it later by giving sensible fallbacks in old ds-versions
* Move layers overview styling to a separate CSS-file
* Adjustments for mobile view
* Make all content on the designsystem front page editable in Sanity
* Further adaption to smaller screen sizes
* Handle promoTag being null
* Remove old patches to link and typography to support contrast variants. Will do this manually through CSS variables for now.
* Remove overrides from link-card css, and using local overrides instead
* Remove commented-out code
* Actually use Sanity data
* Update url
* Move getting started files to a sub-folder
* Move change log files to a sub-folder
* Move aksel-by-numbers files to a sub-folder
* Move ds layers overview files to a sub-folder
* Move support section files to a sub-folder
* Migrate to use new LinkCard component instead of the local one. Had to use Box to add background to the icon, as that has been removed from the component.
* Reverse changes made to this out-dated component, as this code no longer uses it.
* 🚚 Moved data-fetch for changelog to sub-component
* 💄 More spacing between promo and title
* 💄 Removed max-width for layers-panel
* ♻️ Inline gettingstarted-components
* ♻️ Use PageBlock for maxWidth handling
* ♻️ Use LinkCards for mobile layers view
* ♻️ Respect content-type for changelog
* ✨ Use new animated arrow for promo-link
* ♻️ Return notfound if data-loading fails
* 💄 Soft support card bg
* 🐛 Sync teal colors
* 💄 Use last changelog-card as a static link to changelog
* Update aksel.nav.no/website/app/dev/(designsystemet)/designsystemet/page.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Make layers links larger (20px)
* Change support cards to LinkCards
* Aksel by numbers: Change css to css modules
* ChangeLogNews: Change css to css modules
* DS Layers Overview: Change css to css modules
* PromoTag: Change css to css modules
* DSLandingPageHeading: Change css to css modules
* Attempt to add OG image and description
* Suggested typing change from Julian
* Update aksel.nav.no/website/sanity/schema/documents/designsystem-forside.tsx
* Handle null value as well
* Update aksel.nav.no/website/app/dev/(designsystemet)/designsystemet/_ui/change-log/ChangeLogNews.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Update aksel.nav.no/website/app/dev/(designsystemet)/designsystemet/_ui/change-log/ChangeLogNews.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Update aksel.nav.no/website/app/dev/(designsystemet)/designsystemet/_ui/promo-tag/PromoTag.module.css
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* Update aksel.nav.no/website/app/dev/(designsystemet)/designsystemet/_ui/page-heading/DSLandingPageHeading.module.css
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* Can remove css by adding the color token, now that we use BoxNew to add the icon background
* Remove code that was copied from Figma, but does nothing
* Add correct focus styling to PromoTag
* Overriding LinkCard is no longer necessary.
* Filtrer vekk aksel_ds_forside dokumenttypen fra toppnivå i Sanity
* Use semantic colors in PromoTag. Needed to disable eslint-rule because the linter does not find aksel-tokens
* Update aksel.nav.no/website/app/dev/(designsystemet)/designsystemet/_ui/ds-layers-overview/DSLayersOverview.tsx
* Add position: relative to layers link to fix positioning bug in Safari for the rhombus. Also removed redundant/duplicate :last-of-type selector.
* Only show focus-outline for layers links, not the rhombus and the dotted line
* Use same :hover and :focus-visible highlighting for the dotted line and rhombus for all layers links
---------
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* schema for editorialStaff
* 🚚 editorial_staff_teams -> writers
also make it soft-enabled during transition period (hidden + allow incomplete)
* missing rename
* 💄 Flex-start align arrow in LinkCard
* 🐛 Center-align title when no description and footer is set
* 🔥 Removed redundant changesets since component is not yet released
* 📝 Modify story to show flex-start alignment of arrow
* 🎉 Small switch now 20px high for darkside
* 💄 Dynamic loader-size based on checked and switch-size
* 💄 Small size switch for legacy css
* 📝 Changeset
* ⚰️ Add depcation-warning in stylelint-config
* 📝 Changeset
* 🐛 Loader-icon was not centered
* Update @navikt/aksel-stylelint/src/deprecations.ts
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Update .changeset/hip-walls-itch.md
* Update .changeset/flat-numbers-kiss.md
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* ✨ Allow disabling new theme switch and darkside for spesific stories
* 🐛 Change default-value to false
* 🐛 Make sure to force light-mode when showing legacy-stories
* ✨ LinkCard boilerplate
* ✨ Initial setup copying code over from website
* :WIP: Story update
* ⚡ Working mouseevent solution
* 🎉 Standalone card utilities
* 🚚 Moved LinkAnchor utility to separate file
* 💄 Add card css
* 💄 Grid-layout
* 🐛 visited text had wrong color
* 💄 Adjust animations
* 📝 Image sub-component docs
* 📝 LinkCard Stories
* 💄 Add support for data.colors
* 📝 Support chromatic-view
* 📝 Ajust story-example
* 🏷️ Export LinkCard component trough package.json
* 🏷️ Setup sub-component exports
* 🚚 Extract utils into util-dir
* 🚚 Extract LinkAnchor CSS
* ✨ Added LinkCard and LinkAnchor support for legacy
* 📝 Added LinkCard to CSS mappings
* ✨ Add size-prop to title and description
* 📝 Added stories for sizing-prop added to title and description
* ✨ Added size-support in Description
* 📝 Changeset
* ♻️ Update general sizing for type based on LinkCard size
* 📝 Adde uu-demo for testing
* 🐛 Resolve Modal-story bug
* ♻️ Re-write nested CSS in legacy to support postcss
* 📝 JSDoc
* 🐛 Add use-client
* 🎉 Implemented LinkCard Primitive
* ⚰️ Remove LinkCardPrimitive experiment
* 🎉 Compose onClick-events in Anchor util
* 🐛 Remove old stories
* 🏷️ Require href when not using asChild
* Update @navikt/core/react/src/link-card/LinkCard.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* ♻️ Code review cleanup
* 🚚 Move isTextSelected util
* [Examples] Linkcard examples (#3886)
* 📝 LinkCard example code
* ♻️ Avoid rendering arrow when hasArrow is false
* ♻️ Make user add their own img-tag in LinkCard
* 🐛 Allow image to define its own with do avoid CLS when loading
* 🐛 Legacy CSS for linkcard was not synced with darkside
* 📝 More examples
* 📝 Update examples
* 📝 Add example for umami-logging
* ♻️ hasArrow -> arrow
* ♻️ Clean up unused word wrapping css
* 🐛 Center icon when only title is used
* 📝 Added jsdoc directly on sub-components
* 🚚 Moved LinkPanel to deprecated status
* 📝 Added new stories to chromatic
* Update @navikt/core/css/link-card.css
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Update @navikt/core/css/link-card.css
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Update @navikt/core/css/darkside/link-card.darkside.css
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 📝 En -> nb
* 🐛 Use static images to avoid false hits from chromatic
* ⏪ Undo current Link-card css changes
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* :ai: Vibe-code periode status generation
* 📝 Document timeline example
* 📝 Add aria-controls to example
* 🎉 Example periods are more static now
* Update aksel.nav.no/website/pages/eksempler/timeline-intern/dynamic.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 🚧 initial conversion++
* Hero + color animation
* 🚧 infinitely loading?
* 🚧 loading 2/?
* ✨ GpFrontpageCard
* typing++
* regen query types
* disable turbopack
* animations work in dark & light, but if you toggle they get out of sync
* sync animations
* tone down the lines
* page + gpfrontpagecard
* move + use godPraksisPictogram
* 🚧 frontpageLatest
* convert the masonry stuff (wip)
* wip cards (masonry)
* wip cards (masonry)
* wip cards (masonry)
* mostly complete masonry
* cleanup
* tweak widths, better hierarchy with pageblock
* fix broken cube animation
* do not add stylelint rules in this PR
* bit of css insanity to block out the cube animation further down
* use LinkCard for latest news
* hero heading spacing and line height
* colors (gradient and cube) + heroText responsive size
* cube strokes respect pauseAnimation & mobile & responsive icons in gp
* working context state for pausing animations
* LinkCard usage++
* card transition animation fix
* use SEO image (opengraph)
* link fixes (use our Link with as={NextLink})
* design review fixes (#3816)
* minor gradient CSS tweak
* missing border-radius clip on gp og image
* Update Hero.tsx
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* Update Highlight.tsx
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* Update GpFrontpageCard.tsx
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* Update GpFrontpageCard.tsx
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* Update landingpage.module.css
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* PR comment fixes
* sync jsdom dep across workspaces
* ♻️ move and rename components to make more sense
* ♻️
* use non legacy Image + alt texts
* semantic html++ (li inside ul)
* LinkCard can be non-heading
* redundant screenreader info
* simplify shouldStopAnimation a fair bit
* useMemo
* copy cube to /dev/_ui/
* cleanup css
* remove unused CSS use globals.css data- attrib
* use hgrid
* add metadata/OG + minor css cleanup
* use existing LinkCardAnchor + tweak margin for it
* use Layout Component + rename variable
* update:sanity-schema
* improved typing
* type GpFrontpageCard
* redundant className
* LinkCard tweaks
* ♻️ broken file import + 🔥 comment
* better comment to explain hardcoded color values
* add missing type from query
* comments + og title
* fix highlighted cards alignment
* typing++
* 🚧 typing...
* good typing
* update query
* sort might fail if publishedAt is missing, but it should be
* typing++
* typing++
* 🐛 Div -> section for highlighted articles
---------
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
* ✨ Now auto-scrolls to examples when navigated to
* ♻️ Always scroll, even when example was already selected
* Update aksel.nav.no/website/app/_ui/kode-eksempler/KodeEksempler.provider.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 🐛 Avoid re-focusing example from side-effects
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
- Justerte kontrast på fargen 600 (4.51:1 i Leonardo), noen farger er endret.
- La inne MetaPurple i DS Leonardo-fil. Det førte til små justeringer av fargene.
* 🎉 Allow for unique hardcoded pages to appear in global search
* 🐛 Code cleanup
* 📝 Use page metadata for search metadata in routing config
* 🐛 Resolve null-check type error
* 🎉 Rename border-radius tokens to radius with number-naming
* 🔥 Remove border-radius token from darkside tailwind config
* 🚚 Rename border-radius tokens
* 🏷️ Text-icon renamed to text-decoration
* ♻️ Update auto-migration to new decoration token
* ♻️ Migrate text-icon -> text-decoration in components
* ♻️ Allow migrating to default tailwind rounded-tag from legacy aksel-token
* 🔥 Removed throw error for using legacy border-radius token i box
* 📝 Update types to allow for legacy and updated border-radius
* ♻️ Allow for use of both legacy and updated radius token in box
* ♻️ Updated Box-migration to support space-delimited prop-values
* 📝 Changeset
* 📝 Darkside changelog
* 🐛 Add missing manual token-docs
* 🐛 Resolve not updated import after radius filename change
* Delete .changeset/big-goats-report-5.md
* ✨ Support space-separated radius tokens in primitives migration
* ♻️ Temp support legacy radius toke for darkside system
* ✨ Introduce ImageAsThemedSvg for SVG handling in ImageAsSvg.tsx
* 💄 Update background color for dark mode in DesignsystemetOverview.module.css
* 🎉 Use 'themed' thumbnails for ds-pages
* 💄 Change background color to var(--ax-bg-neutral-soft) in DesignsystemetOverview.module.css
* 💄 Move tag to top of overview-card
* 📝 yarn lock sync
* 💄 Tone down overview thumbnail preview
* 🔥 Remove arrow from overview cards
* ♻️ Show arrow on LinkCard
* ✨ Introduce `Studio` component in `Studio.tsx` and `page.tsx` for admin interface
* ♻️ Move Admin and tooling to app-router.
* 🔥 Remove `[[...index]].js` from `legacy-admin` directory
* ♻️ Update sanity to v3.86.1
* 🐛 Setup non-working presentation-mode for sanity
* ✨ Update resolver
* 🎉 Added iframe support for preview in tandem with presentation-mode
* ✨ Integrate `stegaClean` in multiple components for consistent data handling
* 🎉 Render exit draft button
* ✨ Allow for better admin controls and preview
* 🔒 Upgrade insecure requests in production-builds
* 🐛 Update next-sanity to resolve SanityClient type mismatch
* 🐛 Update sanity to 3.87 to fix mismatching sanityclient types
* 🐛 Clean iframe URL to avoid using encoded data, causing errors since url is to long
* ♻️ Use server actions to disable preview mode to stay on page after reload
* 🔥 Remove unused lint-command
* ♻️ Refactor `syncPosition` to use `useCallback` in `CompareImagesProvider`
* ♻️ Refactor dependencies in `GlobalSearchProvider` effect and debounce function
* ♻️ Refactor exports to use type syntax in multiple components
* ♻️ Refactor `articlesMap` access in `page.tsx` to use dot notation
* 🐛 Ignore autogenerated sanity types
* ♻️ Refactor selectedRange rendering in `input-range.tsx`, `input-single.tsx`, and `two-digit-year.tsx`
* ♻️ Refactor `updateConsent` to use `useCallback` in `CookieProvider.tsx`
* 🩹 Ignore lint warning for assignment in expressions
* ♻️ Avoid variable re-assignent in while-loops
* Init new tokens page
* Add token display for (most of) color tokens. Still need a real display example for opaque++.
* Add TableOfContents
* Improve category handling of tokens and add space, radius and shadow
Less reduce and format category on backend
* Fix shadow token example
* Re-introduce role filtering
* Custom sorting for different token types
* Added placeholder comments with text "TODO: Sjur fyller ut" for all (hopefully?) tokens
* Sort token categories
* Add breakpoint tokens
* Make comments a bit smaller in line with the design
* Add font tokens
* Simplify token example code (DRY-principle)
* Remove fallback-comment and add a small gap between token name + rawValue and the comment
* Do not show rawValue for color tokens, as we do not want to encourage using them directly
* Fix issue where we could potentially return undefined from sorting function
* Add breakpoint groups for easily selecting mobile or desktop first media queries
* Simplify how we generate the display value for breakpoint tokens
* Ignore global-color and opacity tokens when generating token config. These will not be displayed on the webpage.
* Fix ToC import after change from main
* Move Tokens toolbar/filter/search to /components
* Add naive token search
* Extract Color token example
* Extract Shadow token example
* Extract Breakpoint token example
* Extract Radius token example
* Extract Space token example
* Extract Font token example
* Refactor TokenExample wrappers
* Extract TokenPreview
* Extract token categories config
* Extract TokenEntry - the presentation of an individual token
* Extract TokenCategory - a collection of of tokens, like background color, radius og font tokens
* Move function for sorting tokens to a util-file
* Don't add Chips wrapper if the category has no roles
* Extract chips for selecting roles
* [Token-docs] Static semantic colors with comments (#3661)
* ✨ Config to create files
* ✨ Config to create files
* ✨ Add semantic role token configurations for info, accent, and danger colors
* 🔧 Refactor semantic role token configuration for consistency
* Add config for token roles and categories. This eliminates the need for some sorting and prettifying of text for titles
* Beskrivelse av tokens på config.ts (#3692)
* Show role descriptions and accept markdown in descriptions for categories, roles and tokens
* This link shouldn't have been pushed
* Tokenbeskrivelse til tokens i mappa /semantic-roles (#3700)
* Update meta-lime.tokens.ts
* Tokenbeskrivelse for accent, brand-beige, brand-blue, brand-magenta og danger.
* Tokenbeskrivelse til info, meta-purple, neutral, success og warning.
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/brand-beige.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/brand-blue.tokens.ts
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/warning.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/accent.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/brand-beige.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/brand-beige.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/neutral.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/brand-blue.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/brand-magenta.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/brand-magenta.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/brand-blue.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/danger.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/success.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/meta-purple.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/meta-lime.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/accent.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update @navikt/core/tokens/darkside/tokens/colors/semantic-roles/info.tokens.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Migrate new tokens page to app router. Changed border and background colors without checking much, so may need to go through those later. Layout is also a bit messed up.
* Add ToC on tokens page
* Layout fixes
* Show "Send feedback" button
* Rename tokens-page.tsx -> TokensPage.tsx
* Use proper type for tokens on TokenPage
* Move search query to url params to allow for use on server side rendered page
* Improve token search by using Fuse
* Filter TableOfContents based on search results
* Add background for opaque tokens. Kinda weak determination that the token is opaque, so that may have to be improved.
* Update tokens to use new darkside tokens as appdir uses this CSS
* Remove default color presentation. Rather log the unknown category for debugging purposes.
* Add functionality for switching between JS and CSS tokens
* Align CopyButton better with text
* Fix search onSubmit, so it does not clear the input
* Use simple Search variant
* Hard-code page header for non-Sanity token-page
* Move darkside tokens page to correct slug (grunnleggende/styling/tokens-darkside) and include it in the left-hand menu
* Filter token roles chips based on search results, so we don't show chips for the roles that has no results
* Token search should ignore where in the results the hits are located, as there are no long texts. A hit is a hit!
* Change multiple instances of Record to just add an "id" property to the objects and place them in an array for easier interaction in the code
* Filter out placeholder token comments and remove placeholder category descriptions
* Move filtering out of the render function
* Add subheadings for roles, and move descriptions to under the subheadings. Also remove chips if there are only one.
* Adjust spacing for tokens list (distance between heading + description vs those and the following list)
* Update aksel.nav.no/website/components/token-docs/token/example/TokenEntry.tsx
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* Update aksel.nav.no/website/components/token-docs/token/example/TokenEntry.tsx
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* Added comments to semantic-root.tokens.ts
* We omit comments for radius, breakpoint, font and space tokens, so comment needs to be optional
* Clarify shadow token use in comment
* Add EmptyStateCard when no tokens are shown
* Vertically center TokenPreview
* Fine-tune search thresholds to provide better results for typos
* Properly define a "root" role for unique tokens and show heading and description. The actual name may be changed later.
* Add support for LESS and SASS tokens as well
* Formatting (line-length)
* Stop setting selected role automatically if filtering causes only one role to be shown. We now remove the chips in this case and always show a heading and description instead.
* Remove "mobile first"/"desktop first" from displayed breakpoint token value, because it hinders copying the text. The same need is covered by the new role heading instead.
* Fix copy-pasted label for TokenFormatSelector
* Refine spacing in line with new Figma sketches to better group content
* Remove "Stabil" tag and "Last updated" text because they do not make sense here. They always apply to the latest version, and then we might have to document outdated tokens somewhere.
* Clean up tokens-darkside page.tsx: remove unnecessary generateStaticParams() and remove awaiting unused params
* Move type into token-docs folder as it is only used there
* Proper typography for token comment
* Reset selected role when searching
* Use static metadata for tokens page, as we don't have any sanity data/parent to receive data from
* Move new tokens page to the "darkside" category
* Remove TODO in description of Meta Purple
* Make Token role Chips neutral to not steal too much attention
* Fix issue where selecting a chip was reverted after half a second, so changed the useEffect to only react to the actual changed search query instead
* Fixed missing gap in mobile view
* Fix bug where changing to another format and back to CSS caused the token name to disappear
* Proper "eyebrow" above page heading (Changed styling -> grunnleggende and color)
* Co-locate tokens page components in app dir
* Use correct heading color for H1
* Update to new darkside tokens
* No longer used
* Add the vk-error class to fallback examples for tokens (where category, group, etc. is unknown) and log a warning in the console
* Add a name to the tokens role chips list, so we inform the user about what they filter
* Disable autocomplete, like for other search fields on the website (main- and icon search)
* Add the correct gap between categories
* Do not use heading tags for token examples to avoid messing with accessibility
* Translate category titles
* Fine-tune search weighting. Still needs more adjustments - likely sorting results, as a "similar result" can now appear before a direct result.
E.g. "avstand" -> Background colors with "standard" in a comment appears before the "Avstand" category tokens.
* Adjust scroll margin so search field is not shown for top category
* Hide token preview from assistive technology
* Add links to Slack and GitHub to "no tokens" block
---------
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Sjur <82579704+sjur-gr@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* 🎉 Added umamiTrack function
* 🚚 Use new umamiTrack function to do manual tracking
* 🚚 Use manual umami tracking onClick for links and logging
* 🏷️ Stronger typing
* 🚚 Manually track URL for navigation events
* 🐛 Use empty object in non-descriptive umamiTrack events
* ⚡ Re-enable Umami
* Update aksel.nav.no/website/app/_ui/umami/Umami.log.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* 📈 Track navigation events in `GpChip` and simplify URL handling in `page`
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* ✨ feat: Add GOD_PRAKSIS_ALL_TEMA_QUERY and related types for improved data fetching
🎨 style: Update globals.css to enhance accessibility for skip links
📝 docs: Enhance theme.d.ts with new data-text-prose attribute for prose text styling
🎉 feat: Implement HeroCube component for visual representation in the hero section
🎨 style: Create Hero.module.css for styling the hero components
🎉 feat: Develop GodPraksisIntroHero component for the hero section
🎉 feat: Create TemaHero component for displaying theme navigation
🎉 feat: Implement HeroCardList for rendering theme cards
🎉 feat: Add HeroIntro component for displaying theme title and description
🎉 feat: Create HeroSelectButton for toggling theme selection
🎉 feat: Build page.tsx to fetch and display themes with the new components
* 🚚 Port over linkcard
* 💄 Replicate gp figma card
* 🎉 Synced hero
* ✨ Implemented GP frontpage
* 🔍 Add god-praksis metadata
* 🎉 Fetch and parse svg from sanity
* 🎉 Update use of linkcard on gp-pages
* 🎉 Use LinkCard on overview-pages
* Update aksel.nav.no/website/app/dev/(god-praksis)/god-praksis/page.tsx
* Update aksel.nav.no/website/app/dev/(god-praksis)/god-praksis/page.tsx
* 🐛 Clean up linkcard types
* ✨ Added data-fetching for tema-pages
* ✨ Gp-hero is now a self-fetching component
* ✨ Migrated state-handling for hero-banner
* ✨ Migrated CSS and interactions for HeroModule
* 🎉 Implemented chip-navigation logic
* 🐛 margin-animation did not work
* 🐛 Used undefined 'open' value
* ⚡ Optimize article mapping
* 📝 Add better metadata to categories
* ✨ Render articles as LinkCard
* ⚡ Fix URL parameter handling in GodPrakisChipsNavigationButton and add prefetch on hover
* 🐛 Chips nav had duplicate data
* 💄 Synced layout with Figma
* 🔍 Added metadata
* 🎨 Refactor padding in search form and improve header logo visibility based on screen size
💄 Add icon styling for GodPraksisHeroButton and create GodPraksisTaxonomyTag component
* 🐛 Add spacing below hero description
* 🐛 Remove spacing from description when no element below
* 🐛 Use data-attrb since last-child selector cant work
* 💄 Use new linear gradient for darkmode hero banner
* 🐛 Add missing focus-markings, word break for title
* 🐛 Use updated colors for fallback image
* [Appdir] God praksis article page Part 1 (#3743)
* 🎉 Scaffolding for gp article pages
* 🐛 Add spacing top to non-first child headings
* ✨ Implemented SystemPanel component
* ✨ Added sanity login check to outdated panel
* [Appdir] God praksis article page Part 2 (#3746)
* ✨ Added authors
* 📝 Formatting
* ✨ Added related articles content
* ♻️ Refactor UnderTemaLink to inline implementation with NextLink
* ✨ Integrate EditorPanel into multiple components and remove unused styles
* 🔍 Add metadata to page
* 🔥 Remove TOC from GP pages
* 🐛 Remove duplicate outdated panel render
* ✨ Add GodPraksisFeedback component with context and states for user feedback
* ✨ Added form-state and validation clientside
* ✨ Added RCS auth
* ♻️ Optimize slack-user lookup, inline inside action
* ✨ Refactor GodPraksisFeedback components and enhance page layout with relevant articles
* 💄 Remove bg from bilde-caption
* 💄 Fix list layout
* 🐛 Editorpanel dir had extra space
* Update aksel.nav.no/website/app/_ui/toc/TableOfContents.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* 🐛 Avoid getting updated types for legacy-implementation
* 💄 Move related links
* Update aksel.nav.no/website/app/_ui/toc/TableOfContents.tsx
* Update aksel.nav.no/website/app/dev/(god-praksis)/god-praksis/artikler/[slug]/page.tsx
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* ✨ feat: Add GOD_PRAKSIS_ALL_TEMA_QUERY and related types for improved data fetching
🎨 style: Update globals.css to enhance accessibility for skip links
📝 docs: Enhance theme.d.ts with new data-text-prose attribute for prose text styling
🎉 feat: Implement HeroCube component for visual representation in the hero section
🎨 style: Create Hero.module.css for styling the hero components
🎉 feat: Develop GodPraksisIntroHero component for the hero section
🎉 feat: Create TemaHero component for displaying theme navigation
🎉 feat: Implement HeroCardList for rendering theme cards
🎉 feat: Add HeroIntro component for displaying theme title and description
🎉 feat: Create HeroSelectButton for toggling theme selection
🎉 feat: Build page.tsx to fetch and display themes with the new components
* 🚚 Port over linkcard
* 💄 Replicate gp figma card
* 🎉 Synced hero
* ✨ Implemented GP frontpage
* 🔍 Add god-praksis metadata
* 🎉 Fetch and parse svg from sanity
* 🎉 Update use of linkcard on gp-pages
* 🎉 Use LinkCard on overview-pages
* Update aksel.nav.no/website/app/dev/(god-praksis)/god-praksis/page.tsx
* Update aksel.nav.no/website/app/dev/(god-praksis)/god-praksis/page.tsx
* ✨ Added theming-support to appdir
* 📝 Added changelog docs for theme class change
* ♻️ Use inline head to set local styles instead of css-modules
* ✨ Setup overview-page
* 🐛 Use inline styles instead of Head element from nextjs
* WIP: V1 list view
* 💄 Align overview heading with regular page heading
* 🐛 Moved margin-bottom to top for overview grid
* 🎉 Add overview toggle for component pages
* 🎉 Added overview-pages to sidebar
* 🎉 Added routing support for overview pages
* 🎉 Setup staticparams and metadata for overview-pages
* 🚚 Moved overview components out to now dir
* ✨ Added overview support for komponenter, templates and grunnleggende
* 🔥 Removed unused sanity-field
* 🐛 Hide fallback-svg
* 🐛 Fix misspelling
* 🎉 Added Iconpage filtering and navigation
* 🎉 Added modal view
* 🐛 Fixed searchParams type
* 🎉 Added empty-state
* 🔥 Removed forced use-client for emptystate
* 🎉 Support svg fetch
* 🔥 Refactor IconPage components and remove unused sidepanel
* 🎉 Added links
* 📝 Added metadata
* 🐛 Resolve impure css module class
* 🐛 Fix iconName validation logic in IconPage component
* 🎨 Improve layout and styling in IconPage components
* 🎨 Update HStack component to use 'as="ul"' for semantic HTML
* ✨ Added icon-page to sidebar
* Update aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.util.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update aksel.nav.no/website/app/dev/(designsystemet)/_ui/icon-page/IconPage.button.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* ⚡ Added zod-validation and debounce to search
* 🏷️ Resolved updated types
* ⚡ Use clientside updates for icon-page
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* ✨ Added theming-support to appdir
* 📝 Added changelog docs for theme class change
* ♻️ Use inline head to set local styles instead of css-modules
* ✨ Setup overview-page
* 🐛 Use inline styles instead of Head element from nextjs
* WIP: V1 list view
* 💄 Align overview heading with regular page heading
* 🐛 Moved margin-bottom to top for overview grid
* 🎉 Add overview toggle for component pages
* 🎉 Added overview-pages to sidebar
* 🎉 Added routing support for overview pages
* 🎉 Setup staticparams and metadata for overview-pages
* 🚚 Moved overview components out to now dir
* ✨ Added overview support for komponenter, templates and grunnleggende
* 🔥 Removed unused sanity-field
* 🐛 Hide fallback-svg
* 🐛 Fix misspelling
* ✨ Setup appdir cookie-banner view
* 🎉 Moved cookie-handling to server-actions
* 🐛 Remove impure css module use
* 🐛 Use forced dynamic to allow consent-banner
* 📝 Add docs
* 🎉 Moved consent doc-page
* ♻️ Rename cookie-actions to use 'action'-suffix
* ✨ Added theming-support to appdir
* 📝 Added changelog docs for theme class change
* ♻️ Use inline head to set local styles instead of css-modules
* ✨ Setup overview-page
* 🐛 Use inline styles instead of Head element from nextjs
* WIP: V1 list view
* 💄 Align overview heading with regular page heading
* 🐛 Moved margin-bottom to top for overview grid
* 🎉 Add overview toggle for component pages
* 🎉 Added overview-pages to sidebar
* 🎉 Added routing support for overview pages
* 🎉 Setup staticparams and metadata for overview-pages
* 🚚 Moved overview components out to now dir
* ✨ Added overview support for komponenter, templates and grunnleggende
* 🔥 Removed unused sanity-field
* 🐛 Hide fallback-svg
* 🐛 Fix misspelling
* Update aksel.nav.no/website/app/dev/(designsystemet)/_ui/sidebar/Sidebar.tsx
* 🐛 Could not create overview-pages for grunnleggende or templates
* Remove unused CSS that causes issues when Combobox' ToggleListButton is skipped
This code removed wrapping of chips if ToggleListButton is hidden, and will only ever apply to the button otherwise. That does not need a flex layout.
* Add changeset
* ✨ Added theming-support to appdir
* 📝 Added changelog docs for theme class change
* ♻️ Use inline head to set local styles instead of css-modules
* 🐛 Use inline styles instead of Head element from nextjs
* 🚧 box migration scaffolding
* 🚚 boxnew -> darkside/boxnew
* 🚧 wip
* 🚧 working single substitution (pending lookup in replacements)
* ✅ working test
* import rename in output test
* ✅ import rename + tests
* use config comments, better import example
* commentblock function (append to string output) + update tests (failing)
* 🚧 almost good... needs to respect renamed imports
* ✅ passing tests
* ♻️ organize some utils
* 🧪 Added more tests
* cast JSXIdentifier type
* pass idempotency tests
* also refactor the usage when renaming a function
---------
Co-authored-by: Ken <ken.aleksander@gmail.com>
* ♻️ Update TOC feedback, add layout padding bottom
* 🐛 Added missing type
* 🐛 Fix missing spacing in intro-section
* 🎉 Added metadata for changelog view support in mønster pages
* 🎨 Refactor layout styles and enhance sidebar button animations
* 🎉 Added SEO support
* 🐛 Add page SEO image to start of image list
* 🔥 Remove reduntant early exit code
* 🐛 Open feedback in new tab
* 🐛 Update footer to use responsive layout
* 🐛 Moved theme-colors to viewmport config
* 🐛 Show mobile-menu earlier
* 🐛 Use local schema in sanity-typegen
* 📝 Typo
* ✨ Implemented codeblock header and codeblock
* ✨ Can now toggle betweem different code-views
* 🐛 Use Theme component to get color-scheme in CSS
* 💄 Updated padding, use custom theme
* ✨ Implemented custom prism-theme
* ✨ Use new prism-theme for propsseksjon
* 🎉 Use new codeblock for single code view
* 🐛 Add hover-effect for expander button
* ✨ Added provider and navigation support
* WIP: Add codeview block
* ✨ Enhance KodeEksempler with Suspense, toolbar, and improved props handling
* 🎨 Refactor CodeBlock component by removing unnecessary Theme wrapper and improving className handling
* ✨ Use local styles for kodeexample module
* 🐛 Add useclient
* 🐛 Use local css variable since css counter breaks at random
* ✨ Set up page-slug query logic
* ♻️ Inline TableOfContents in smaller modules
* 🎨 Refactor TableOfContents styles and update component structure
* 🎨 Enhance TableOfContents styles and structure for improved responsiveness and clarity
* ✨ Enhance TableOfContents component with feedback and changelog links; refactor queries for improved data handling
* 🚚 Co-localized layout in grouped ui dir
* ✨ Extract schema types from sanity
* 🐛 Update sidebar types to use new sanity schema-types
Bug: icon was marked as "new tab" but the icon for new tab is an box with the arrow stopping at the border. Having the new tab synonym here will cause missunderstandings.
Bug: icon was marked as "new tab" but the icon for new tab is an box with the arrow stopping at the border. Having the new tab synonym here will cause missunderstandings.
* root layout
* ✨ Added draftmode
* ✨ Setup boilerplate
* ✨ Setup grouping-dir
* ✨ Implemented header
* 💄 Made header sticky
* 🔒 Content security policy updated
* 🚚 Moved favicon to appdir
* 🔥 restore old frontpage
* 🚚 Moved sidebar to appdir
* ✨ Implemented sidebar loading
* ✨ Doc lyout
* 🐛 Add id for skiplink
* ✨ Added new footer
* 💄 Move skiplink further away
* 💄 Add more padding bottom sidebar
* ✨ Refactor header and sidebar components for improved layout and structure
* 📝 Uncomment some code
* 🔥 Removed user server directive from component and layouts
* 🐛 Center footer
* 🐛 Re-added support for chromatic connect
* 🔥 removed browser-token
* Update aksel.nav.no/website/app/_ui/footer/Footer.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* [Aksel.nav.no] Ported over global search to app-router (#3663)
* 🚚 Moved search-code closer to app router
* ✨ Added button and modal
* ⚡ Initial loading for recent articles
* ⚡ Query data from the server
* ✨ Generates parsed search-results
* ⚡ Added debounce
* ✨ Render hits
* ♻️ Limit articles for each field to 20
* 🐛 Revert form-layout to initial view
* 🐛 Build searchindex before website
* ✨ Pre-select text when re-opening search
* ✨ Enhance global search UI with improved layout and styling
* ✨ Refactor global search components to improve search functionality and UI
* ✨ Improve global search functionality and UI responsiveness
* ✨ Update global search UI and functionality with improved input handling and styling
* ✨ Refactor global search dialog and add Kbd component for keyboard shortcuts
* ⚡ removed reduntant suspense
* ✨ Refactor global search components for improved styling and functionality
* ✨ Refactor global search results rendering for improved performance and readability
* ✨ Update global search dialog and form for improved styling and accessibility
* ✨ Refactor global search components for improved layout and styling
* ✨ Refactor GlobalSearchHitCollection for improved styling and accessibility
* 🎨 Update global search configuration and styles for improved clarity and aesthetics
* 🎨 Update layout and styles for global search components for improved aesthetics and readability
* ✨ Refactor global search utilities and update styles for improved clarity and maintainability
* 🔧 Refactor global search queries and implement caching for improved performance and data retrieval
* ⚡ Preload cache for search
* 🔧 Refactor fetchArticles function to improve caching logic and remove unnecessary logging
* 🔥 Removed user server directive from component
* 🔥 Removed comments
* ⚡ Explicitly declare server-only component
* Update aksel.nav.no/website/app/_ui/global-search/GlobalSearch.fetch.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update aksel.nav.no/website/app/_ui/global-search/GlobalSearch.fetch.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* 📝 Rename sanitiezedata
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* [Aksel.nav.no] Move Aksel-brand theme into app-router for use (#3668)
* 💄 Added global aksel-theme tokens
* ✨ Implement new Aksel-brand for theming
* [Aksel.nav.no] Updated MobileMenu for new ds-pages (#3670)
* 💄 Added global aksel-theme tokens
* ✨ Implement new Aksel-brand for theming
* ✨ Initial setup mobile-menu
* ✨ V1 mobile-menu component
* 💄 Update mobile navigation and global search responsiveness; adjust overlay color opacity
* 💄 Refine modal backdrop animation timing and opacity for smoother transitions
* 💄 Adjust pressed-sidebar state
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* 📝 Reposition sanity fields
* ✨ Added new standalone-category to sanity docs
* ✨ Added validation to make sure standalone articles dont overlap with sections
* 🐛 Always error out if using standalone articles until implemented
* 🔥 Removed temp removal of feature
* 🐛 Check for undefined slug
* [Aksel.nav.no] Implemented support for new lvl 1 articles (#3658)
* ✨ Added support for new lvl 1 articles
* 🧪 Added tests for new sidebar
* 🔥 Removed breadcrumb from WithSidebar layout
* 🐛 Revert to prod dataset
* ✨ Setup darkside migration
* ✨ Can now convert primitives spacing
* 🧪 Added more tests
* 📝 Docs for utils
* 📝 Changeset
* 📝 Typos
* [Darkside] ✨ Added migration for css, less and scss (#3598)
* ✨ Added migration for css, less and scsss
* Update @navikt/aksel/src/codemod/migrations.ts
* ✨ Make old px spacing space-1
* ✨ Use esbuild to inline local references
* ✨ Added js migration to space-tokens (#3600)
* ✨ Setup first iteration of token translation object
* ✨ Started mapping old to new tokens
* ♻️ Simplified repalcement logic
* ✨ Added tests for token-migration
* ✨ Replaces old token with new reference
* ✨ Handle overrides of tokens
* 🧪 Global messages storage
* 📝 Added migration
* 🐛 Initialize message list
* ✨ Customizable results messaging
* ✨ Customizable results messaging
* 🐛 Use replacement, not ref
* 📝 Only document found legacy-tokens, dont rename them
* 📝 Better comments
* ✨ Added context to remaining token updates
* ✨ Added comments to unsupported token
* ✨ Format comments better, remove duplicates
* 📝 Success message if no legacy-tokens found
* 🐛 Return updated source files
* 🐛 Reference valid replacement
* 🔥 Removed comments
* ✨ Added better messaging
* ✨ Better messaging structure
* 🧪 Added more tests
* 🧪 Tests for less, scss, css and js tokens
* 🐛 CSS hits were shown as scss and less hits
* 📝 Format messages better
* 📝 Format messages better
* ✨ Added js-migrations template
* Update test runner
* 🐛 Revert mistakes
* 🐛 Resolve testing rig
* ✨ Js token migration
* 📝 Better docs for JS migration
* 📝 Added missing lineshift
* ✨ Tooling setup
* ✨ Tooling setup with enquirer
* ✨ Tooling setup with enquirer
* ✨ Set up inquirier and enquirer to promt user for tasks
* ✨ Get legacy-data
* ✨ Now formats and prints progress
* ✨ Now formats and prints progress
* ⚡ Smarter scanning of updated tokens
* ⚡ Refactored status-code
* ✨ Now prints remaining files
* ✨ Smarter tailwind-regex
* 📝 Tell user we are scanning the files
* ✨ Moved transforms to new darkside location
* 🚚 Moved tests for js transforms
* 📝 Updated prettier config to ignore new test-files
* 📝 Moved css, less, scss tests
* 🐛 Resolved tests not running properly on less,scss setup
* WIP: tailwind config migration
* 📝 Added tailwind config for border-radius
* ✨ Strictly match tailwind-classes
* ♻️ Convert status obj to class
* ♻️ Compact regex parsing
* ♻️ Simplified regex-handling for new token lookup
* ✨ Added support for component tokens
* ⚡ Optimize lineNumber task
* 🐛 Inline regex-probe
* 🐛 Remove old merged code
* 🐛 Fix regex escape-characters
* Update @navikt/aksel/src/darkside/index.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* 🐛 Fix token-tests
* ✨ Now prints with token-name
* ♻️ Move files
* ♻️ Move printing to TokenStatus class
* ✨ Add comments to printout
* 🐛 Handle word boundaries
* ⚡ Pre-generate the regexes
* 🧪 More regex-tests
* 🧪 More regex-tests
* ⚡ Use built-in regexes
* ⚡ Only run codeshift on relevant files
* 🐛 No print for codemod status update
* ⚡ Only run codeshift on relevant files
* 📝 Added extra space
* 📝 Exit after codemod
* 📝 Removed temp logging
* 🐛 Await codeshift execution
* 🧪 Added tests for not imported JS-tokens
* ✨ Implemented tailwind migration code
* 🧪 @apply tests for tailwind transform
* 🐛 Handle spaces better for tailwind config
* 🐛 Handle prefixing of tailwind config correctly
* 🐛 Handles cases where empty collection was passed
* 🐛 makes sure to clean up match before parsing
* 🧪 More tailwind-codemod tests
* ⚰️ Removed moved code, renamed darkside -> spacing for migrations
* ♻️ Config cleanup part 1
* ♻️ Config cleanup part 2
* ♻️ Refactored runner
* ✨ Added run all migraitons task
* 🐛 Use destrucutred taskname
* 📝 Better naming
* 📝 More spesific import docs
* 🐛 Print filename correctly when token contains special characters
* 🐛 Update spacing migrations paths
* 🐛 Added new spacing-tokens to legacy config
* ✨ Initialize status directly in constructor
* ⬆️ yarn lock sync
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* 🐛 Handle cases where to is defined and from is undefined
* 🐛 Fix missing state-option when selecting ranged datepicker values
* Update @navikt/core/react/src/date/datepicker/DatePicker.stories.tsx
* Update @navikt/core/react/src/date/datepicker/DatePicker.stories.tsx
* 🧪 Added tests for new ranged-datepicker logic
* 🐛 Typo
* 🎨 Adjust padding in alert styles for consistency
* 🎨 Update chat bubble padding for improved consistency
* 🎨 Standardize padding in date and modal styles for consistency
* 🎨 Refactor padding in error summary styles for improved consistency
* 🎨 Adjust padding in file upload styles for improved consistency
* 🎨 Update padding in file upload styles for improved consistency
* 🎨 Adjust transition duration and padding for form progress and summary styles
* 🎨 Update padding in guide panel styles for improved consistency
* 📝 Changeset
Endret i darkmode
Brand Blue og Success, 100 - 400, endret saturation til 100.
Accent, 100 - 400, endret saturation til 60.
Alle farger 600, endret til kontraststop til 4.52:1. Bug, noen farger havnet under 4.5:1.
Alle farger 1000, endret saturation til 100.
* 🚧 umami test script
* 🚧 CSP for umami
* 🚧 test data-tag, set no auto tracking
* ♻️ make consentBanner more self contained
* 🚧 working implementation for consent modal
* ♻️ move ConsentBanner to website-modules + small changes
* use typescript-cookies over localStorage API
* Copy Nav.no consent text
* better name for cookie
* 🚧 crude complex consent object
* Update aksel.nav.no/website/components/website-modules/ConsentBanner.tsx
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* classify traffic better (umami)
* Update aksel.nav.no/website/components/website-modules/ConsentBanner.tsx
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* link to our own personvernerklering + disable modal on that specific page
* use same conditions for all showModal() triggers
* update comment
* limit cookie to specific subdomain (prevent it getting sent by clients to other *.nav.no pages
* remove the MutationObserver, but keep query param to disable banner (for tests)
* bypass banner in e2e tests
* store "no action" consent data in sessionStorage
* 🚧 change to in document flow style banner (no more modal)
* 📱 responsive consent banner++
* website: set cookie domain dynamically (from serving site)
* hardcode /side/personvernerklaering
* 🚧 placeholder cookie preferences box
* cookie preferences box++
* dokmøte plenumsendringer
* move page personvernserklaering
* make the cookie preference form functional
* ♿ add missing aria-labelledby target
* re-enable banner on e2e search tests
* Add explanation to organic and polluted traffic
* extract consent state into custom hook
* PR review changes
* ✨ Serverside-check for cookie consent
* banner set tracking id from env var
* remove unused url param check
* env var toggle for PRODUCTION
use this to inject code scripts clientside, do stuff during build or anything else really! (better to use this than NODE_ENV)
---------
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Lars Eirik Korsgaard Hansen <71271458+larseirikhansen@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
* ✨ Setup darkside migration
* ✨ Can now convert primitives spacing
* 🧪 Added more tests
* 📝 Docs for utils
* 📝 Changeset
* 📝 Typos
* [Darkside] ✨ Added migration for css, less and scss (#3598)
* ✨ Added migration for css, less and scsss
* Update @navikt/aksel/src/codemod/migrations.ts
* ✨ Make old px spacing space-1
* ✨ Use esbuild to inline local references
* ✨ Added js migration to space-tokens (#3600)
* ♻️ Use eslint-bundle as main build for CLI
* ♻️ Add mappings exports in ds-css, avoid using esbuild
* 💄 Update background Header
* 💄 Update popover padding for smaller screens
* 💄 Adjusted borders
* 💄 Uses Alpha-tokens for zebra-stripes in Table
* 💄 Stronger border for hovered timeline items, transparent track bg
* 💄 Transparent lines for Tabs to contrast colored bg
* 💄 New icons for Table-sorting
* 💄 Selected exoanded item now has accent colors
* 💄 Keeps border-color for active state in search-button
* 💄 Removed hover-color change on checked radio
* 💄 Only non-responsive or left-static guidepanels shrink on tablet or below
* 📝 Changeset
* 🚚 -hover tokens are now soft-hover
* ✨ Added soft-pressed token
* 💄 Use new soft-pressed token where applicable
* 💄 Default colored text is now 1000, strong replaced by subtle
* 🏷️ Updated primitive types for new text-tokens
* 🚚 Migrated old text-role-strong color to text-role-default
* 🚚 Migrated referansesider to new text-colors
* 🚚 Referansesider uses new soft-hover token
* 🐛 Fallback to updated accent-color
* [Darkside] Removed 'base' as neutral role replacement (#3556)
* ♻️ Remove default-scale as replacement for neutral in config
* 🧪 Setup chromatic-run for complete build
* 🚚 Text migrated
* 🚚 Text icon migrated
* 🚚 Text contrast migrated
* 🚚 bg soft migrated to neutral
* 🚚 bg moderate migrated to neutral
* 🚚 bg strong migrated to neutral
* 🚚 border default migrated to neutral
* 🚚 border subtle migrated to neutral
* 🚚 border strong migrated to neutral
* ⬅️ Revert chromatic preview
* 🚚 Migrated referansesider to new neutral scale
* 🚚 Use named brand and meta-colors
* 🚚 Referansesider uses new brand-names token
* 💄 Update background Header
* 💄 Update popover padding for smaller screens
* 💄 Adjusted borders
* 💄 Uses Alpha-tokens for zebra-stripes in Table
* 💄 Stronger border for hovered timeline items, transparent track bg
* 💄 Transparent lines for Tabs to contrast colored bg
* 💄 New icons for Table-sorting
* 💄 Selected exoanded item now has accent colors
* 💄 Keeps border-color for active state in search-button
* 💄 Removed hover-color change on checked radio
* 💄 Only non-responsive or left-static guidepanels shrink on tablet or below
* 📝 Changeset
* 🚚 -hover tokens are now soft-hover
* ✨ Added soft-pressed token
* 💄 Use new soft-pressed token where applicable
* 💄 Default colored text is now 1000, strong replaced by subtle
* 🏷️ Updated primitive types for new text-tokens
* 🚚 Migrated old text-role-strong color to text-role-default
* 🚚 Migrated referansesider to new text-colors
* 🚚 Referansesider uses new soft-hover token
* 🐛 Fallback to updated accent-color
* [Darkside] Removed 'base' as neutral role replacement (#3556)
* ♻️ Remove default-scale as replacement for neutral in config
* 🧪 Setup chromatic-run for complete build
* 🚚 Text migrated
* 🚚 Text icon migrated
* 🚚 Text contrast migrated
* 🚚 bg soft migrated to neutral
* 🚚 bg moderate migrated to neutral
* 🚚 bg strong migrated to neutral
* 🚚 border default migrated to neutral
* 🚚 border subtle migrated to neutral
* 🚚 border strong migrated to neutral
* ⬅️ Revert chromatic preview
* 🚚 Migrated referansesider to new neutral scale
* 💄 Update background Header
* 💄 Update popover padding for smaller screens
* 💄 Adjusted borders
* 💄 Uses Alpha-tokens for zebra-stripes in Table
* 💄 Stronger border for hovered timeline items, transparent track bg
* 💄 Transparent lines for Tabs to contrast colored bg
* 💄 New icons for Table-sorting
* 💄 Selected exoanded item now has accent colors
* 💄 Keeps border-color for active state in search-button
* 💄 Removed hover-color change on checked radio
* 💄 Only non-responsive or left-static guidepanels shrink on tablet or below
* 📝 Changeset
* 🚚 -hover tokens are now soft-hover
* ✨ Added soft-pressed token
* 💄 Use new soft-pressed token where applicable
* 🚚 Referansesider uses new soft-hover token
* 💄 Update background Header
* 💄 Update popover padding for smaller screens
* 💄 Adjusted borders
* 💄 Uses Alpha-tokens for zebra-stripes in Table
* 💄 Stronger border for hovered timeline items, transparent track bg
* 💄 Transparent lines for Tabs to contrast colored bg
* 💄 New icons for Table-sorting
* 💄 Selected exoanded item now has accent colors
* 💄 Keeps border-color for active state in search-button
* 💄 Removed hover-color change on checked radio
* 💄 Only non-responsive or left-static guidepanels shrink on tablet or below
* 📝 Changeset
* 🚚 -hover tokens are now soft-hover
* 🚚 Referansesider uses new soft-hover token
* 💄 Update background Header
* 💄 Update popover padding for smaller screens
* 💄 Adjusted borders
* 💄 Uses Alpha-tokens for zebra-stripes in Table
* 💄 Stronger border for hovered timeline items, transparent track bg
* 💄 Transparent lines for Tabs to contrast colored bg
* 💄 New icons for Table-sorting
* 💄 Selected exoanded item now has accent colors
* 💄 Keeps border-color for active state in search-button
* 💄 Removed hover-color change on checked radio
* 💄 Only non-responsive or left-static guidepanels shrink on tablet or below
* 📝 Changeset
* 💄 Stepper text-colors
* 💄 Adjusted paddings form-summary
* 💄 Alpha-border formsummary
* 🐛 Re-added animation to formprogress
* 💄 moderate active state for button
* ♻️ Copybutton re-uses button-component
* 💄 Updated shadow for lightmode
* 🔥 Removed comments CopyButton
* 📝 Added bw-compatibility for CopyButton
* 🐛 Added default-size for legacy CopyButton
* 🐛 Boolean flipped so that legacy wrapper were used on wrong state
* 🐛 Boolean flipped so that legacy wrapper were used on wrong state
* 🐛 Now accounts for custom icons in legacy CopyButton
* Getting started converting ActivityPlan to Aksel components
* Refactor ActivityColumn
* attempt to add Theme (from local path)
* 🚧
* 🐛 minify strips navds-r-p?
* use correct react package (local) and use padding prop
* correctly use the Theme wrapper in ThemeProvider
* Refactored MainCard
* Make ActivityCard more similar to "the real deal".
* Reduce column padding
* use Link component
* add missing href to links in sykepenger
* remove generic seletors on root
* dark mode for sykepengericon (also use tokens)
* Fixes console warnings
* Added activity view for activity plan reference page
* Design review changes
* Add HelpText to columns
* Add focus indicator for activity cards
* Minor css tuning
* 🐛 Blogg did not sort latest articles by publishDate
* [Actions] ⚡ Optimize workflows (#3480)
* 🚚 Moved auth-token, fetch less history
* ♻️ Use composite actions for workflow
* 🐛 Checkout before running composite workflow
* 🚚 Move composite actions to separate dir
* ♻️ Pass token to composite action
* 📝 Better test names
* ⚡ Optimize chromatic workflow
* ⚰️ Removed old and broken redeploy action
* ⚡ Optimized website-build
* ♻️ Use composite actions for prod and dev build
* ⚡ Reduced e2e tests by 40%
* ⚡ Improved sandbox testing
* ⚡ Update search-tests
* 🐛 Disable invalid example-demos
* 🐛 Wait for search completion
* 📝 removed old env input
* 🐛 Moved vars out to input
* 🐛 Don't ignore inputs for if-tests
* 🐛 Check input for truuthynes
* test: Run sharded playwright
* 🐛 Removed sharded workflow
* ⚡ Update re-use of actions
* 🐛 Keep full git-history for chroamtic
* Update .github/workflows/chromatic.yml
Co-authored-by: Lars Eirik Korsgaard Hansen <71271458+larseirikhansen@users.noreply.github.com>
* Update .github/workflows/referansesider-spa-deploy.yml
Co-authored-by: Lars Eirik Korsgaard Hansen <71271458+larseirikhansen@users.noreply.github.com>
* Update .github/workflows/ci.yml
Co-authored-by: Lars Eirik Korsgaard Hansen <71271458+larseirikhansen@users.noreply.github.com>
* Update .github/workflows/ci.yml
Co-authored-by: Lars Eirik Korsgaard Hansen <71271458+larseirikhansen@users.noreply.github.com>
* Update .github/workflows/ci.yml
Co-authored-by: Lars Eirik Korsgaard Hansen <71271458+larseirikhansen@users.noreply.github.com>
* Update .github/workflows/ci.yml
Co-authored-by: Lars Eirik Korsgaard Hansen <71271458+larseirikhansen@users.noreply.github.com>
* Update .github/workflows/ci.yml
Co-authored-by: Lars Eirik Korsgaard Hansen <71271458+larseirikhansen@users.noreply.github.com>
* Update .github/workflows/ci.yml
Co-authored-by: Lars Eirik Korsgaard Hansen <71271458+larseirikhansen@users.noreply.github.com>
* 📝 Typo
* 📝 Typoo
* ⚡ Use built-in report for playwright
* 🐛 Use html-reporter, not list
---------
Co-authored-by: Lars Eirik Korsgaard Hansen <71271458+larseirikhansen@users.noreply.github.com>
* [Darkside] P 1/3 ♻️ Updated `default` scale to replace `neutral`-scale (#3487)
* ✨ Added new space tokens
* 🚚 Updated spacing-definitions
* ✨ Added new space-tokens to old system
* ✨ Added support for new space tokens i primitives
* 🔥 Removed space-tokens from tailwindcss
* 📝 Removed old comments
* [Darkside] Spacing -> Space update for CSS (#3478)
* 🚚 Migrated to new space-token
* 📝 Add docs for temp spacing
* 🐛 Add missing token-prefix to docs
* 🐛 Remove old tests
* 🐛 Added legacy-spacing support for primitives when using new theme-stystem
* ♻️ Move neutral-scale -> default scale
* ♻️ New box now supports updated default tokens
* ♻️ Default -> Soft token
* 📝 Update js-doc
* [Darkside] P 2/3 ♻️ Global/Meta-token naming, bumped role-scale (#3499)
* 💄 Text and border-tokens for roles bumped up
* 📝 Update global and meta-colors to numbers, removed hover color on accordion/button
* [Darkside] P 3/3 ♻️ Old neutral-scale now uses default-scale (#3488)
* ♻️ Use default-tokens for old neutral-scale
* 🐛 Fixed missing token migrations
* ♻️ role default -> role-soft
* ♻️ Re-structure order and texts for GP section (#3492)
* ♻️ Re-structure order and texts for GP section
* 📝 Avoids extra capitalization for names
* Added script to check version-sync between workspace (#3486)
* ✨ Added script for verifying dep-versions across workspace
* ✨ Added script for verifying dep-versions across workspace
* ✨ Added script for verifying dep-versions across workspace
* ✨ Added script for verifying dep-versions across workspace
* ✨ Added script for verifying dep-versions across workspace
* 📝 Better texts
* 📝 Better texts
* 📝 Added comments
* 📝 Updated minoer package-differences
* 📝 Upgraded stylelint
* 📝 Upgraded babel-loader
* 📝 Add ^ to ds-css versionsing across repo
* ♻️ Small refactor to reduce use of forEach
* Table: Removed border-bottom for HeaderCell used in ExpandableRow (#3503)
* Use HeaderCell for name in expandable table example
* 🐛 Removed border-bottom for HeaderCell used inside ExpandableRow
---------
Co-authored-by: Ken <ken.aleksander@gmail.com>
* Version Packages (#3505)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* ♻️ Removed raised-tokens from darkside roles
* 💄 Incremental layout and list-view update on Produktbloggen
* 🐛 Added missing 'li'-tag for list-item in blogglist
* [Table] ExpandableRow `contentGutter`-prop (#3507)
* ✨ New prop contentGutter for ExpandableRow component
* ✨ Added new prop-support in darkside CSS
* 📝 changeset
* Update @navikt/core/react/src/table/ExpandableRow.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 🐛 Third bloggpost was sliced from view on frontpage
* ⬆️ Bump vite from 5.4.8 to 5.4.12 in /examples/referansesider (#3516)
* ⬆️ Bump vite from 5.4.8 to 5.4.12 in /examples/referansesider
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.8 to 5.4.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.12/packages/vite)
---
updated-dependencies:
- dependency-name: vite
dependency-type: direct:development
...
Signed-off-by: dependabot[bot] <support@github.com>
* Repo-bump vite
* 🔀 Yarn lock
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
* 📝 Added new sidebar category for /komponenter
* always render the maxSelected message when isMultiSelect (#3506)
* always render the maxSelected message when isMultiSelect
* also check if maxSelected is set
* Changeset
* Update .changeset/swift-readers-check.md
---------
Co-authored-by: Lars Eirik Korsgaard Hansen <71271458+larseirikhansen@users.noreply.github.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* 💄 Error borders uses danger-strong consistently (#3512)
* ♻️Fix#3451 scaling for 400% zoom (#3514)
* 🐛 Using 'loader' inside 'icon'-prop broke layout caused by position absolute (#3515)
* [Darkside] Quality assurance P1 (#3510)
* 💄 Adjustments 1
* 💄 Bumped default strong scale
* 💄 Bumped secondary neutral border
* 💄 Reduced text-color change on hover
* 🐛 Resolved border and focus logic for combobox
* 🐛 Fix icon sizing for buttons
* 💄 Use tertiary-buttons for datepicker caption
* Update @navikt/core/react/src/chips/Toggle.tsx
* 💄 QA updates combobox darkside (#3511)
* Update swift-readers-check.md
* Version Packages (#3509)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* Update LICENSE
* [Darkside] Updated Focus design for Checkbox, Radio and Switch (#3513)
* 💄 Focus-outline now around item checkbox,radio
* 🐛 Resolved small and indeterminate-state
* 💄 updated focus-markings for switch
* Update @navikt/core/css/darkside/form/radio-checkbox.darkside.css
* 🐛 Hide new indeterminate node from old checkbox
* [Darkside] Quality assurance P4 (#3518)
* 💄 Adjust bg transparency
* 💄 Default border now same scale as roles
* [Darkside] Quality assurance P5 (border-radius and font-weight) (#3519)
* 💄 Update border-radius based on Figma
* 💄 More border-radius adjustments
* 💄 Bolder medium+ headings
* [Darkside] Updated focus-outline (#3520)
* 💄 Updated focus-outline
* 🧪 Added legacy-support for deprecated files
* 🐛 Account for legacy files in count
* [Darkside] Quality assurance P6 (Fileupload) (#3524)
* 📝 Update FileUpload CSS-artifacts
* 🐛 Synced release-text with updated role-colors
* ✨ New icons for sorting (#3528)
* Theming for sykepenge icon
* 🐛 Use correct global token for darkside JS
* Updated SykepengerIcon to be one shape
* Changed text color to subtle
* Layout fixes
* Refactor activity modal and improve rendering of activity details
* Remove the decorator from the activity plan reference page to reduce complexity
It is not necessary to show off dark mode, and it introduces some problems we need to solve.
Will keep the code, so we can re-introduce it later if we want.
* remove route listing, redirect index.tsx to aktivitetsplan
* re-add the switcher
* resulotions for ds-react, and tweak spacing for header
* Fill out more activity details
* remove unused files + fix up some old tokens
* Darker modal backdrop
* Make JobInterestBox stand out more in dark mode
* Adjust heading size to better preserve hierarchy, although the modal header should have been larger instead (not supported)
* Add gap for better responsiveness on mobile
* Revert to secondary button to not conflict with other primaries. May revisit this later
* Increased Page.Block gutter tokens
* Background softA is now working, so we don't need to check for which is the active theme
* Better responsiveness
* Add "Add new activity" modal
* Reduce texts for responsiveness
* Make "save" button narrower
* 🚧 test deploy cache-invalidation
* 👷 test again
* 💄 remove border under ThemeSwitch
* Delete .changeset/late-hotels-try.md
* Delete @navikt/core/css/darkside/primitives/page.darkside.css
* Revert "Delete @navikt/core/css/darkside/primitives/page.darkside.css"
This reverts commit e1d683898a.
* undo the change that will be done on main
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Vegard Haugstvedt <it.vegard@gmail.com>
Co-authored-by: Ken <ken.aleksander@gmail.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Lars Eirik Korsgaard Hansen <71271458+larseirikhansen@users.noreply.github.com>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Accordion: change: do animation similar to ExpansionCard
* changeset
* change: comment expansioncard -> accordion
* don't need changeset for unreleased code
* 🚧 attempt to smooth out expansion of accordion
* working animation
* Update @navikt/core/css/darkside/accordion.darkside.css
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* accordion: remove border-bottom on content, add on item
* Update @navikt/core/css/darkside/accordion.darkside.css
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* Accordion: only set box-shadow line for indented items
---------
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* 💄 Focus-outline now around item checkbox,radio
* 🐛 Resolved small and indeterminate-state
* 💄 updated focus-markings for switch
* Update @navikt/core/css/darkside/form/radio-checkbox.darkside.css
* 🐛 Hide new indeterminate node from old checkbox
* Use HeaderCell for name in expandable table example
* 🐛 Removed border-bottom for HeaderCell used inside ExpandableRow
---------
Co-authored-by: Ken <ken.aleksander@gmail.com>
* ✨ Added new space tokens
* 🚚 Updated spacing-definitions
* ✨ Added new space-tokens to old system
* ✨ Added support for new space tokens i primitives
* 🔥 Removed space-tokens from tailwindcss
* 📝 Removed old comments
* [Darkside] Spacing -> Space update for CSS (#3478)
* 🚚 Migrated to new space-token
* 📝 Add docs for temp spacing
* 🐛 Add missing token-prefix to docs
* 🐛 Remove old tests
* 🐛 Added legacy-spacing support for primitives when using new theme-stystem
* ♻️ Move neutral-scale -> default scale
* ♻️ New box now supports updated default tokens
* ♻️ Default -> Soft token
* 📝 Update js-doc
* [Darkside] P 2/3 ♻️ Global/Meta-token naming, bumped role-scale (#3499)
* 💄 Text and border-tokens for roles bumped up
* 📝 Update global and meta-colors to numbers, removed hover color on accordion/button
* [Darkside] P 3/3 ♻️ Old neutral-scale now uses default-scale (#3488)
* ♻️ Use default-tokens for old neutral-scale
* 🐛 Fixed missing token migrations
* ♻️ role default -> role-soft
* 📝 Better naming for heading in richtext editor
* ♻️ Better syntax for publish-action
* ♻️ Refactored publication-flow plugin
* 🚚 Moved quality-assurance texts
* ♻️ Better handling of verification flow
* 🐛 Fixed verification without publish action
* 🐛 Check for permissions before assuming publish will work
* ✨ Added new space tokens
* 🚚 Updated spacing-definitions
* ✨ Added new space-tokens to old system
* ✨ Added support for new space tokens i primitives
* 🔥 Removed space-tokens from tailwindcss
* 📝 Removed old comments
* [Darkside] Spacing -> Space update for CSS (#3478)
* 🚚 Migrated to new space-token
* 📝 Add docs for temp spacing
* 🐛 Add missing token-prefix to docs
* 🐛 Remove old tests
* 🐛 Added legacy-spacing support for primitives when using new theme-stystem
* 📝 Updated JSDOC
* Automatically add JSDoc with default values to nb.ts
* Added jscodeshift to devDeps
* 🐛 Use same jscodeshift versions
* 🐛 Use same types for jscodeshift
---------
Co-authored-by: Ken <ken.aleksander@gmail.com>
* WIP
* WIP CompareImages
* ✨ Handle more keys
* ♻️ Re-use positioning code
* 💄 Adjust for width
* ✨ Added sanity schema
* 💄 Moved focus to box, allow handle to overflow leftside by 1 px
* ✨ Added bg and alt-text support
* 🐛 Change env back to production
* Check loading prop for showing spinner, not widthOverride.
Making this change because widthOverride sometimes gets set to 0 (should also be fixed), meaning this check fails as "falsy".
* Prevent setting width to 0 while showing spinner, to avoid issue in Modals
Checking that width is truthy (not zero) will avoid issue where widthOverride gets set to 0 because the button is not yet rendered.
* Add changeset
* Replace width calculation with just visually hiding label + icon and absolutely positioning the loading icon.
* Update @navikt/core/react/src/button/Button.tsx
* add story for bug case
* We no longer use buttonRef/mergedRef internally, so removed them and set ref directly
* Update @navikt/core/css/button.css
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* Hiding everything except loading spinner is more robust than hiding every specific item.
* In loading-state, the button icon will no longer be able to be the only child (hidden), because of the loader icon. Instead checks for no label.
Could consider :not(:has(:is(.navds-button__icon, .navds-loader))) as well, to be more robust, but it might be overkill.
* Much simpler/easier to read
* Don't need to destructure style, only to add it along with rest.
* Should destructure onKeyUp from props, instead of having it added through filteredProps and then overridden afterwards.
* Quick and easy conversion for darkside
* Unnecessary :is after refactoring
---------
Co-authored-by: Julian Nymark <julian.nymark@nav.no>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
* 💄 New ToggleGroup design
* 🐛 Removed hover-state from selected togglegroup in forced-colors mode
* 💄 Add px-gap to elements
* 🐛 Hide divider on both sides when selected
* change: add story to demonstrate buggy textarea behaviour inside modal
* use lodash debounce with our own default value for wait
* Revert "use lodash debounce with our own default value for wait"
This reverts commit 7638297765.
* change: add leading to debounce + use it in TextareaAutoSize
* changeset
* changeset + co-author
Co-authored-by: Halvor Haugan <halvor.haugan@nav.no>
---------
Co-authored-by: Halvor Haugan <halvor.haugan@nav.no>
* change: support icon in ErrorMessage + warning icon for textarea error
* change: ignore icon on old CSS + tweaks
* change: do not nest in old css
* wip commit (works nicely)
* clean up css for errormessage, remove it from form.css
* 🐛 tweaks + use icon in Search
* use icon for the rest of our form components
* backport new css to old form.css
* 🔥 remove unnecessary specification bump
* cleaner example story
* changeset
* ♻️ icon -> showIcon
* make flex & gap opt-in with 'icon'
* change: inline ErrorMessageIcon
* change: nest css more
* revert test changes (utility in multiple variants on one for DOM diffing, but probably no longer useful)
* debloat changeset
* mention form components in changeset
* change: typo in changeset
* change: typo in changeset
* ✨ Solves issue where programatic focus to fix error would show dropdown list and hide error
* 🚚 Moved onclick listener for open-state to input, not wrapper
* Update .changeset/forty-rules-ring.md
* change: timeline periods
* update stories for timeline
* change: Timeline pin update
* change: crude variable renaming (--a- -> --ax-)
also likely done with zoom buttons,
a bit of popover styling fix
* change: popover z-index bump
* change: remove unnecessary CSS state :active
* change: z-index fix, correct token usage
* change: remove @supports not selector(:focus-visible)
* change: use proper tokens
* change: can't get focus on popover
* change: remove arrow from popover consumers
* change: only render arrow div when !themeContext
* 💄 Use smaller offset without arrow, use subtleA border for popover
* 💄 Adjusted popover tokens for timeline
---------
Co-authored-by: Ken <ken.aleksander@gmail.com>
* ✨ Added darkmode-support to Tag
* 🐛 Update spacing-tokens to new format
* ✨ Added darkmode-support to Tag
* 💄 use canvas-color for text on filled forced color text
* ♻️ Use classNames for tag colors, not data-attrb
* 🐛 Old Tag-css supports new names
* V1 of CSS bundler
* V1 of CSS bundler
* ♻️ Added target checking for browser
* ♻️ Remove built in variables added by lightningcss
* Setup bundling of darkside
* 🐛 Fix path resolving in bundle script
* Setup bundling for index
* 🚚 Re-order layering
* ✨ Filter for only components in bundle
* 🚚 Moved token-definition
* 🔥 Removed index.css copy
* ✨ Global build
* ✨ form and primitives build
* ✨ Component build
* ✨ Build versions dir for new setup
* :test_tupe: Added tests for bundle
* 🚚 Move bundle file to config
* 📝 Comments
* ✨ Allow import of darkside directly under ds-css
* 🐛 Use relative import for CSS in storybook
* [Darkside] Updated input-bg in darkmode, added token for opacity (#3441)
* [Darkside]: Update bg-input to be darker in darkmode
* Added opacity-token
* Use token for disabled opacity
* Update @navikt/core/css/darkside/button.darkside.css
* ♻️ De-dupe reading of index.css
* ♻️ Use regex to remove - and space instead of lodash
* 🥅 Throw when bundler fails
* Update @navikt/core/css/config/bundle.darkside.ts
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Update @navikt/core/css/config/bundle.darkside.ts
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 🏷️ Updated opacity-tokens types, resolved import conflict
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* ✨ Updated exports to allow better darkside support
* ✨ Setup less and scss support for darkside
* ✨ Added static build for tokens
* ✨ Token-config exports
* 🐛 Resolved import conflicts between tailwind and css
* 🐛 Resolved import conflicts between tailwind and css
* ♻️ Refactor Token-setup for better typing
* ♻️ WIP resolve import conflicts2
* ♻️ Avoid importing JSON in react from tokens
* 🐛 Resolve updated imports in referansesider after refactor
* Update @navikt/core/tokens/.gitignore
* Update @navikt/core/tokens/types.ts
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 🐛 Removed prefix 'ax' when generating static tokens for js
* 🐛 Resolved export syntax to support less, scss and JS
* 📝 Updated JSDoc to link directly to types
* Update @navikt/core/tokens/darkside/figma/figma-config.types.ts
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* ♻️ Use colorRole-array as bases for Role array
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
- Merged LanguageProvider into Provider, while also changing the API a bit to make it easier to use (with the help of TS).
- Updated tests for `useI18n()` according to changes in API.
- Chips.Removable: Removed prop `removeLabel`.
- DatePicker/MonthPicker: Added new `translations` prop and deprecated `locale` prop.
- Search: Deprecated prop `clearButtonLabel`.
- Textarea: Deprecated prop `i18n`.
- Timeline: Deprecated prop `axisLabelTemplates`.
- nb/nn/en.ts:
- Arranged the top level keys alphabetically.
- Merged `close` in Modal and DatePicker and put it under the `global` key.
- Changed Search.clear from "Clear" to "Clear field" to make it more... clear 😅
- Removed Combobox.clear since the clear button has been removed.
- Made them importable from `@navikt/ds-react`
* GuidePanel initial CSS rewrite
* ✨ new illustration
* change: layout
* change: add speechbubble arrow
* change: dark mode raised for speechbubble arrow
* change: correct stroke path color (actually fill) of path in svg
* change: fine tuned SVG with padding and overlapping paths
* change: exception CSS for FF rendering of speech bubble tail
* change: hardcode calculation as whole pixel value remove FF custom CSS
* change: convert BEM modifiers to simpler CSS data-attributes
* change: display:none for speech bubble tail on old token system
* change: refactor old css to use the attribute selectors
* change: noSVG example (see default story for side-by-side)
* change: rewrite to using only SVG for speechbubble
* change: only looks good in lightmode, no token support / hardcoded
* :refactor: Updated Guidepanel CSS to use SVG
* 🔥 Remove old commented CSS
* 🐛 Fixed cases where mediaqueries would target nesten element instead of itself
* 🗑️ Cleanup of unused code
---------
Co-authored-by: Ken <ken.aleksander@gmail.com>
* ✨ Added darkmode support to table
* 📝 Added comments
* 📝 Added todo
* :refactor: Avoid use of custom animate height function
* :refactor: Use Alpha-token on hover-selected
* :refactor: Remove redundant token-use
* Update @navikt/core/css/table.css
* ♻️ Reverted to animate-component for table animation
* 📝 Overlap comments
* WIP: update primitives prefixing
* ♻️ Use updated prefix in primitives ts-code
* ♻️ Use updated token-prefix in css
* ♻️ Use updated tokens in primitives css utilities
* 🔥 Removed backwards-compat with old stack CSS
* [Darkside] Primtives prop-support (#3423)
* :refactor: Support primitives with new theme
* :refactor: Use sub-component for new Box update
* ♻️ Update support for new and old theme in Page and Box
* ♻️ Resolve instances where tokens were not correctly named for theme-support
* ♻️ Avoid exposing component before necesarry
* ♻️ Avoid having to import figma-types in ds-react
* ♻️ Using camelCase on tokens breaks primtives. Now uses kebab-case
* 🔥 Removed comment used as notes
* Update @navikt/core/react/src/layout/page/Page.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Update @navikt/core/react/src/layout/box/Box.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* ♻️ Remove redundant prefix const
* ♻️ remove redundant T-suffix on types
* ♻️ Better previews on hover when checking token-props
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* ♻️ More fine-grained exception-testing for use of old props in Box
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 🚧 wip
* [Darkside] ErrorSummary updated CSS (#3331)
* First run for errorsummary css update
* Added nesting
* [Darkside] Fileupload CSS update (#3343)
* ✨ Updated Dropzone with darkmode
* ♻️ Added nesting for root
* ♻️ ac -> acx prefix for interla tokens
* ♻️ Removed reduntant token
* ♻️ More nesting
* 💄 Align overlay on drag better
* ⚰️ Removed test-code
* 💄 Update hover-border for dropzone
* 🐛 Removed border-color update on hover in disabled state
* ♻️ Use bg input on area, use border default
* [Darkside] Link CSS update (#3353)
* ✨ Darkmode-support for existing link-component
* :refactor: Adjusted line offsets and thickness
* [Darkside] ExpansionCard CSS update (#3338)
* 💄 Initial css-update expansioncard
* 📝 Added sub-headers
* 💫 Button animation
* ✨ Added data attrb for open-state
* 💫 Updated content animation
* ♻️ Added nesting
* ♻️ Better nesting of rules
* ♻️ Use outline for hover border
* ♻️ Use border as default outline
* 🔥 Remove redundant border css
* 🔥 Removed divider transition, unused state attributes in react
* [aksel.nav.no] Update examples of Page (#3349)
* Changesets: Run yarn as part of create-version
* Combobox: Fix issue where using arrow keys in list would make the entire page scroll (#3364)
* [Darkside] FormSummary updated CSS (#3345)
* :refactor: Use updated CSS for darkside
* 💄 Replace accent bg with info bg
* [Docs] Use NAV-dekorator for Page examples (#3362)
* [aksel.nav.no] Update examples of Page
* comment
* comment + rename + style->class
* fix
* 📝 Use NAV-dekorator for Page-examples
* 🚚 Moved content to separate __parts location
* :refactor: Updated examples
* Update aksel.nav.no/website/components/website-modules/examples/__parts/PageDemoContent.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* 🐛 Use correct import syntax for parts
* 📝 Add description to page-block default story
* 💄 Removed spacing from all typo-headings in demo
* 🐛 Removed react import from content part
---------
Co-authored-by: Halvor Haugan <halvor.haugan@nav.no>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Git hooks that notify when yarn.lock have changed (#3361)
* 💄 first pass css updates
* 🎨 nest CSS
* Datepicker: 💄 css layer changes & remove extra border
* 🔥 remove resolved todo
* 💄 border radius for caption buttons
* 🎨 drastically simplify focus outline
* change: make button selector less wildcardy
* change: outline smaller for rdp-cell buttons
* change: width large enough to show 4 characters in year caption
* change: match design for range middle selected
* change: remove redundant error CSS
* change: remove unused CSS
* change: un-shuffle css import ordering for date.css
* change: remove unused CSS
* change: week buttons & day button active
* change: use secondary-neutral button for weeknumber buttons
* change: same selector for month and day buttons, active state
* 🐛 Fixed weeknumber as the button broke old datepicker
* change: remove double border on captions, active state on disabled
* ♻️ Updated weeknumber to be barkw-compat with old system
* 🚚 Moved date-import further up
---------
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
Co-authored-by: Halvor Haugan <halvor.haugan@nav.no>
Co-authored-by: Ken <ken.aleksander@gmail.com>
Hid buttons from screen readers since you can't tab to them anyways. Moved sr-only text on clear button to title attr. Removed sr-only text on toggle list button. Removed prop toggleListButtonLabel.
* Prevent active text being announced waaay too many times.
* Add changeset
* New attempt at consistently reduced status messages on "copy" click
* Remove aria-relevant and aria-atomic to make announcement consistent between Safari and other browsers
* When single select on combobox, remove the gap between the input and the selected value.
* ♻️ Updated spacing between options and input in Combobox
* 📝 Changeset
* Revert darkside update
* ♻️ Update darkside combobox
* :refactor: Make gap something we add, not remove for chips based on type
* Update .changeset/yellow-days-rhyme.md
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* :refactor: Only add margin-left when only-child
---------
Co-authored-by: Ken <ken.aleksander@gmail.com>
Co-authored-by: Ken <26967723+KenAJoh@users.noreply.github.com>
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
- [Darkside]: Use shadow-dialog token in components, use subtle-border in combobox list
- [Darkside]: FileSummary nesten items border
- [Darkside]: Adjusted ExpansionCard padding
- [Darkside]: ActionMenu uses new Radio-design
- [Darkside]: Added shadow-token to system
* :refactor: Added darkmode-support to Stepper
* :refactor: Restructure Stepper to support darkmode and nesting
* 🐛 text-decoration now changes on hover
* :refactor: Line before non-interactive step now has border-neutral
* ✨ Added brandvolume support in theme-component
* 📝 Added brandvolume story to readmore
* ✨ Added large size to readmore
* ✨ Added darkmode support for Readmore
* :refactor: Updated CSS syntax
* 🐛 Fixed brandvolume stories
* ✨ Implemented brandvolume in ReadMore
* ✨ Added large-variant of readmore to OG readmore
* :refactor: Use tokens and calc to handle volume
* 🐛 Added text-accent-strong on hover to high brandvolume
* 🐛 Avoid adjusting readmore body when size is large
* 📝 CHangeset
* Update @navikt/core/react/src/provider/theme/AkselTheme.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* Update @navikt/core/react/src/provider/theme/AkselTheme.tsx
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
---------
Co-authored-by: Halvor Haugan <83693529+HalvorHaugan@users.noreply.github.com>
* ✨ Added darkmode support in react for internalheader
* :refactor: Support darkmode theming for internalheader
* 💄 Added bottom border to header, divider now subtleA
* 🐛 Remove hover,active effect in static title
* :refactor: Remove redundant :is for button
* :refactor: use padding shorthand
* 💄 Improved hover-effect on forced-color buttons, added story
* 🐛 Revert back to is button syntax
* :refactor: Updated List css to support darkmode
* ♻️ nest some CSS
* 🐛 Updated stories
* 🐛 Removed removed story from chromatic view
* :refactor: Removed color-override on list-markers
* :refactor: Set icon-color to currentColor
* :refactor: Removed currentColor from icon
---------
Co-authored-by: Julian Nymark <julian.nymark@nav.no>
* :refactor: Initial loader update
* :refactor: Re-added support for inverted
* :refactor: Added nesting to loader
* 🐛 Foreground now scales the same as background track
* Updated checkbox css
* bug: Added correct icon-color for readonly checkbox
* Form-css: Updated form.css and fieldset.css with new tokens
* Added radio dot support
* use 4px border-radius, use border-strong for error
* Error-state updated for checked checkbox
* Updated 'dot'-color for readonly radio
* Updated radio-design
* Added disabled-stories for checkbox and radio
* Radio story with checked and error
* Updated foced-colors support for radio
* Use border-syntax instead of box-shadow
* Removed old code
* Updated checked color to accent-strong-pressed for radio
* Adjusted Error-colors for radio/checkbox
* 💄 Radio can now be checked and have Error
* ♻️ Use hoverA bg for hover
* ♻️ Readio readonly now matches figma
* [Darkside] Migrated typo to new CSS
* memo: Added comments
* change: Implemented accordion header and content
* change: Added lines
* feat: added todo
* change: Fixed layering issues with lines
* change: Avoid animation on initial render
* change: Removed comments
* change: moved icon state into nesting
* change: Added alpha-background for icon, updated fontweight
* change: Added forced-colors support
* Removed use op --open class
* Removed old grid centering
* Top, bottom line is now alpha
* Fixed animation
* bug: Revert to transparent bg
* deprecation: Added tags to accordion props that are to be removed
* removed deprecation for accordion headingSize prop
* Updated implementation of line in accordion
* Remove position relative in accordion content
* Implemented size-update for accordion
* bug: removed old comment
* Clarify comments
* Removed redundant header class
* Re-added className in accordionHeader
* Use new tokens in copybutton
* Updated animation
* updated to nested syntax
* Updated text-color on hover
* Added subheading for sectioning
* removed background none
* sanity config cleanup
* removed extra const for type
* re-organized admin tools and fixed types
* Removed no longer needed custom actions for translations
* Simplified publication plugin
* Removed extra logic in publish action
* synced SEO-updates
* lock sync
* bug: Removed old index export
For versjonering bruker vi løsningen [Changesets](https://github.com/changesets/changesets) for [versjonering](https://semver.org/) og publisering av pakker.
Vi bruker [Changesets](https://github.com/changesets/changesets) for [versjonering](https://semver.org/) og publisering av pakker.
## Howto
## Lage ny versjon
- For å lage en ny versjons-entry:`yarn changeset`
- Velg pakkene som skal versjoneres med `arrowkeys` + `Space`
1.`yarn changeset`
2. Svar på spørsmålene som kommer opp:
`🦋 Which packages would you like to include?`
Trykk `Space` for å velge pakkene som skal versjoneres, for så trykke`Enter` for å gå videre.
Trykk `Space` for å velge pakkene som skal versjoneres, trykk så`Enter` for å gå videre.
`🦋 Which packages should have a major bump?`
Trykk `Enter` for å gå videre _*eller*_ velg pakkene som skal marjor-bumpes med `arrowkeys` + `Space`
Trykk `Enter` for å gå videre _eller_ velg pakkene som skal marjor-bumpes med `arrowkeys` + `Space`.
`🦋 Which packages should have a minor bump?`
Trykk `Enter` for å gå videre _*eller*_ velg pakkene som skal marjor-bumpes med `arrowkeys` + `Space`
Trykk `Enter` for å gå videre _eller_ velg pakkene som skal minor-bumpes med `arrowkeys` + `Space`.
Hvis du ikke velger major eller minor, så blir pakkene få en patch-bump
Hvis du ikke velger major eller minor, så vil pakkene få en patch-bump.
- Commit og push.
`Please enter a summary for this change`
Skriv inn en kort beskrivelse av endringen på formatet `<Komponent>: <gitmoji?> <Beskrivelse>`, f.eks. "Button: ✨ Add feature xyz".
3. Commit og push.
## Fixed versjonering
Alle base-pakkene våre har `fixed versjonering`. Dette tilsier at en bump på en av disse vil bumpe alle til samme versjon
Alle base-pakkene våre har `fixed versjonering`. Dette betyr at en bump på en av disse vil bumpe alle til samme versjon
* - Convert to new format (v9): https://eslint.org/docs/latest/use/configure/migration-guide
* - Look into opinionated stylistic plugin
* - Replace @typescript-eslint/recommended with @typescript-eslint/recommended-type-checked
* - We should add "plugin:react/jsx-runtime", but not sure if this will cause problems for projects not using the new JSX transform
* - Consider adding the rule "id-length"
*/
/**
* V9 migration WIP
* - eslint-plugin-storybook does not support v9 yet: https://github.com/storybookjs/eslint-plugin-storybook/issues/157
* - eslint-plugin-testing-library does not support v9 yet: https://github.com/testing-library/eslint-plugin-testing-library/issues?q=is%3Aissue+is%3Aopen+v9
* - @vitest/eslint-plugin supports v9, just need to replace @vitest/legacy-recommended: https://github.com/vitest-dev/eslint-plugin-vitest?tab=readme-ov-file#usage
* - Looks like @typescript-eslint/eslint-plugin and @typescript-eslint/parser can both be replaced by just typescript-eslint in v9: https://typescript-eslint.io/getting-started#step-2-configuration
*/
module.exports={
env:{
browser:true,
es6:true,
node:true,
},
extends:[
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"plugin:storybook/recommended",
],
/* Allows eslint-plugin-react to detect installed react-version */
settings:{react:{version:"detect"}},
rules:{
"import/no-unresolved":"off",
"react/jsx-curly-brace-presence":[
"error",
{propElementValues:"always"},
],
"array-callback-return":"error",
"object-shorthand":"error",
"no-else-return":"error",
"no-console":[
"warn",
{
allow:[
"info",
"warn",
"error",
"group",
"groupEnd",
"table",
"assert",
"countReset",
"count",
"dir",
"time",
"timeEnd",
"timeStamp",
],
},
],
/* Temporarily turned off until code is updated */
Ultra-compressed communication mode. Cuts token usage ~75% by speaking like caveman
while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra.
Always use caveman for code-related questions unless spesificed otherwise. For non-code topics, use caveman if user seems to want quick, concise answers.
"Component re-renders because you create a new object each render. Use useMemo."
full:
"New object each render → new ref → re-render. useMemo."
ultra:
"inline obj → new ref → re-render. fix: useMemo."
## auto-Clarity
Drop caveman for: security warnings, irreversible action confirmations, multi-step sequences where fragment order risks misread, user asks to clarify or repeats question. Resume caveman after clear part done.
Example - destructive op:
> **Warning:** This will permanently delete all rows in the `users` table and cannot be undone.
>
> ```sql
> DROP TABLE users;
> ```
>
> Caveman resume. Verify backup exist first.
## boundaries
Code/commits/PRs: write normal. "stop caveman" or "normal mode": revert. Level persist until changed or session end
description:Structured content modeling guidance for schema design, content architecture, content reuse, references versus embedded objects, separation of concerns, and taxonomies across Sanity and other headless CMSes. Use this skill when designing or refactoring content types, deciding field shapes, debating reusable versus nested content, planning omnichannel content models, or reviewing whether a schema is too page-shaped or presentation-driven.
---
# Content Modeling Best Practices
Principles for designing structured content that's flexible, reusable, and maintainable. These concepts apply to any headless CMS but include Sanity-specific implementation notes.
## When to Apply
Reference these guidelines when:
- Starting a new project and designing the content model
- Evaluating whether content should be structured or free-form
- Deciding between references and embedded content
- Planning for multi-channel content delivery
- Refactoring existing content structures
## Core Principles
1.**Content is data, not pages** — Structure content for meaning, not presentation
2.**Single source of truth** — Avoid content duplication
3.**Future-proof** — Design for channels that don't exist yet
4.**Editor-centric** — Optimize for the people creating content
## References
Start with the reference that matches the modeling decision in front of you, instead of loading every topic at once. See `references/` for detailed guidance on specific topics:
-`references/separation-of-concerns.md` — Separating content from presentation
-`references/reference-vs-embedding.md` — When to use references vs embedded objects
-`references/content-reuse.md` — Content reuse patterns and the reuse spectrum
-`references/taxonomy-classification.md` — Flat, hierarchical, and faceted classification
Organizing content with taxonomies enables filtering, navigation, and content relationships. Well-designed taxonomies scale; poorly designed ones become maintenance nightmares.
## Types of Classification
### Flat Taxonomy
Simple list of terms with no hierarchy.
**Use for:** Tags, simple categories
**Example:** Blog tags: "javascript", "react", "tutorial"
```typescript
defineType({
name:"tag",
type:"document",
fields:[
defineField({name:"title",type:"string"}),
defineField({name:"slug",type:"slug"}),
],
});
```
### Hierarchical Taxonomy
Terms with parent-child relationships.
**Use for:** Product categories, content sections
**Example:** Electronics > Phones > Smartphones
```typescript
defineType({
name:"category",
type:"document",
fields:[
defineField({name:"title",type:"string"}),
defineField({name:"slug",type:"slug"}),
defineField({
name:"parent",
type:"reference",
to:[{type:"category"}],
description:"Parent category (leave empty for top-level)",
}),
],
});
```
### Faceted Classification
Multiple independent dimensions.
**Use for:** Complex filtering (e-commerce)
**Example:** Filter by color AND size AND price range
description:"Restructure a ds-react component's files into canonical folder/naming conventions. Use when reorganizing a component's file layout, splitting a monolithic component file, or renaming files to match domain conventions."
argument-hint:"Path to component folder, e.g. @navikt/core/react/src/accordion"
---
# ds-component-restructure
Restructure a `ds-react` component folder to follow canonical conventions — without breaking the public API.
## Canonical Structure
```
<component>/
├── index.ts # "use client" + re-exports from root
├── <Component>.stories.tsx # Storybook stories
├── <Component>.tests.stories.tsx # Storybook play-tests (only if play tests exist)
├── <Component>.meta.ts # Meta info for component. Used for documentation generation.
├── root/
│ ├── <Component>Root.tsx # Main compound component + sub-component namespace
│ ├── <Component>Root.context.ts # Context (if shared across sub-components)
│ ├── <Component>Root.test.ts # Unit tests for root (if needed)
│ └── use<Hook>.ts # Files for root-level hooks (if needed)
├── <subcomponent>/ # One dir per sub-component
│ ├── <Component><Subcomponent>.tsx
│ ├── <Component><Subcomponent>.test.ts # Unit tests for sub-component (if needed)
│ └── use<Hook>.ts # Files for sub-component-level hooks (if needed)
├── helpers/
│ ├── <helper-name>.ts
│ └── <helper-name>.test.ts # Unit tests co-located with helper
└── <Component>.types.ts # Shared types (if needed across multiple files)
| Unit test file | `<filename>.test.ts(x)` | `useAccordion.test.ts` |
| Meta file | `<Component>.meta.ts` | `Accordion.meta.ts` |
| `index.ts` | always `index.ts` | `index.ts` |
## Procedure
### 1. Read the Component
Read every file in the component folder — `index.ts`, all `.tsx`/`.ts` source files, meta file, stories, and test stories. Ignore inconsistencies in file naming and structure; the goal is to fix those, not preserve them. Use the `component`-field in the meta file to determine the proper naming if unsure.
**Done when:** every file's role (component, hook, type, context, test, metadata) is known, and every symbol currently exported from `index.ts` is recorded.
### 2. Map Files to Target Structure
Build this table before touching any files:
| Old path | New path | Renamed? | Publicly exported? | Importers to update |
- List every file that imports it (grep for the filename or exported symbol)
- Flag circular dep risks: context importing sub-components, helpers importing component files, types importing non-types
Resolve circular deps before proceeding: move shared pieces to a lower-level file (types → `.types.ts`, utilities → `helpers/`).
**Done when:** every file in the component folder has a row in the table, and every circular-dependency risk is resolved or explicitly noted.
### 3. Create New Files
For each row in the mapping table where the path changes:
1. Create the file at the new path with all import paths updated to reflect new locations
2. Update every file in the "Importers to update" column — use the mapping table, don't rely on memory
3. Do NOT delete old files yet
> **Case-only renames** (path differs only in letter casing, e.g. `heading.stories.tsx` → `Heading.stories.tsx`) must NOT use the create-new + delete-old flow. On case-insensitive filesystems (macOS and Windows default) the two paths resolve to the **same file**, so creating the new file overwrites the old and the later "delete old" step destroys your content — and git records no rename. Handle these with the two-step `git mv` in the [Case-only renames](#case-only-renames) edge case instead.
### 4. Update Stories
Move stories to component root dir. If stories import from internal paths, update those paths.
**Exception:** A `stories/` subdirectory is acceptable if it already exists — do not flatten it.
**Casing:** Story filenames follow `<Component>.stories.tsx` (PascalCase). When the existing file is lowercase (e.g. `heading.stories.tsx`), this is a **case-only rename** — follow the [Case-only renames](#case-only-renames) procedure, do not create + delete.
### 5. Update `index.ts`
All exports are **named** — no `default` exports anywhere. The component `index.ts` re-exports the compound component, its sub-components, and their types from the root file:
```ts
"use client";
export{
<Component>,// compound root (named)
<Component>Trigger,// sub-components (named)
// ...
}from"./root/<Component>Root";
exporttype{
<Component>Props,
<Component>TriggerProps,
// ...
}from"./root/<Component>Root";
```
Group values first, then types. If types live in `<Component>.types.ts`, re-export them from there.
#### Compound root file export pattern
In `<Component>Root.tsx`, assemble the compound component with `Object.assign` — **do not** use an `as <Component>Component` cast or a dedicated `<Component>Component` interface:
Why `Object.assign` instead of `as`: the cast types the const as an interface that has no runtime `valueDeclaration`, so `react-docgen-typescript` (used by `yarn docgen:meta`) fails to extract props and can't document the component when it is exported via a grouped `export { }` statement. `Object.assign` lets TypeScript infer the intersection type from the value, so metadata generation works with named, bottom-of-file exports.
Sub-component files follow the same rule — named value export plus named type export at the bottom, no inline `export interface` and no default:
Update imports in `<Component>.meta.ts` to reflect new paths. If the meta file imports from the component's `index.ts`, no changes are needed.
If component has no meta file, create one in the component root with the following content based on existing patterns.
Note that each standalone component should have a meta file. But a "sub-component" (like `AccordionItem`) does not need a meta file.
### 7. Validate No Breaking Changes
**Export diff.** Before deleting old files, compare the exported names recorded during Step 1 against the new `index.ts`. Every name must be present:
- Exports in new index.ts that are missing from original → regression
- Exports in original that are missing from new index.ts → missing export (breaking change)
Concretely: grep for `export` lines in both versions and diff them. All component names and type names must match exactly.
**Build.** Run `yarn workspace @navikt/ds-react build` — must succeed with no TypeScript errors.
**Package root.** Verify `@navikt/core/react/src/index.ts` still re-exports the component. No changes needed there unless the component's `index.ts` path changed (it shouldn't).
**No case-duplicate tracked paths.** This is the most common restructure bug. On case-insensitive filesystems (macOS, Windows) git can end up tracking **two** entries that differ only in casing (e.g. `accordion.stories.tsx`**and**`Accordion.stories.tsx`) while the working tree shows only one file. The stale entry then breaks tests and checkout on other machines. `ls` / file search will NOT reveal it — you must ask git. Run:
Any output is a leftover. Remove the stale entry from the index by its **exact old path** (this does not touch the correctly-cased file on disk):
```sh
git rm --cached '<exact old lowercase path>'
```
Then confirm only the intended casing remains, e.g. `git ls-files '*<component>*stories*'`.
**Done when:** the export diff is empty, the build succeeds, the package-root re-export is verified, and `git ls-files` reports no case-duplicate paths.
### 8. Remove Old Files
Only after verifying new files are correct and `index.ts` exports match the original, delete the old files.
Skip any rows already moved with `git mv` (case-only renames and other `git mv` moves) — there is no separate old file to delete for those.
### 9. Unit Tests
Unit tests live next to the file they test — same directory, same base name with `.test.ts` / `.test.tsx` suffix.
Do NOT move existing test files unless their source file moves. When moving a source file, move its test file with it and update import paths.
## Edge Cases
### Case-only renames
A rename that changes **only letter casing** (e.g. `heading.stories.tsx` → `Heading.stories.tsx`, or `accordion.tsx` → `Accordion.tsx`) is a trap on case-insensitive filesystems (macOS and Windows defaults):
- Both paths point to the **same** file, so `create_file` at the new path overwrites the old content, and the Step 8 "delete old" then removes it entirely.
- git does not see a rename (`core.ignorecase=true`), so history is lost and other machines get checkout/case-collision conflicts.
Do the rename in **two `git mv` steps through a temporary distinct name** so git records it and it works on every filesystem:
Then edit the file's contents/imports in place. Skip the create-new + delete-old flow for these rows entirely.
Guidelines:
- Prefer `git mv` for **all** moves/renames (not just case-only) so history is preserved; only fall back to create + delete when the file content is being split or substantially rewritten.
- Detect case-only renames while building the Step 2 mapping table: compare old vs new path lowercased — if they're equal but the raw paths differ, mark the row as case-only.
- The temporary name must differ by more than case (add `.tmp`), otherwise the same collision reoccurs.
### Flat components without sub-components
Leave flat components flat - do NOT introduce subdirs unless the component contains internal utility-components (files that render JSX but are not public sub-components). If such a utility-component exists, move it to its own subdir following the canonical naming.
Example: `button/Button.tsx` has no other sub-components, so should stay in root.
### `parts/` directory
If the component uses a `parts/` dir, break it up into separate sub-component dirs.
### Internal sub-components
Sub-components that are implementation details (not exported publicly) use the same dir/file naming as public ones (see Naming Rules) with an `Internal` suffix, but must NOT be exported from `index.ts`:
```
backdrop/
└── DialogBackdropInternal.tsx ← internal, NOT exported from index.ts
```
### Nested subcomponents
Do NOT nest subcomponent dirs inside other subcomponent dirs. All sub-component dirs must be direct children of the component root:
```
# Wrong
dialog/
└── header/
├── DialogHeader.tsx
└── title/
└── DialogTitle.tsx
# Correct
dialog/
├── header/
│ └── DialogHeader.tsx
└── title/
└── DialogTitle.tsx
```
## Constraints
- **No breaking changes.** Public export names and prop shapes must be identical after restructuring.
- **Named exports only.** No `default` exports; group named exports at the bottom of each file (values first, then types).
- **Compound roots use `Object.assign`.** No `as <Component>Component` cast or `<Component>Component` interface — it breaks `yarn docgen:meta` prop extraction.
- **React 17 compatible.** No React 18/19-only APIs. Import React explicitly in `.tsx` files.
- Keep JSDoc on public props and components. The component-level JSDoc block sits directly above the `Object.assign` call; sub-component `@see` tags go on the keys inside the `Object.assign` object literal.
Turn on Cache Components in a Next.js app and resolve the blocking routes it
surfaces. Use when the user wants to enable, adopt, or migrate to Cache
Components, flip the `cacheComponents` flag, work through a flood of
blocking-prerender / instant validation errors, run the
`cache-components-instant-false` codemod, or decide between opting routes out
with `export const instant = false` and fixing them in place.
---
# next-cache-components-adoption
Enable Cache Components on an app and walk it to a passing build. This skill sequences the work; per-error recipes live in the dev overlay fix cards and the build's terminal output. The [migrating to Cache Components guide](https://nextjs.org/docs/app/guides/migrating-to-cache-components) is the canonical reference for the concepts and per-API recipes this skill applies — consult it whenever the skill steps reference a pattern (`"use cache"`, `cacheLife`, `<Suspense>` placement, etc.) and you want the full explanation.
## requires
- **App Router project.** Cache Components is an App Router feature; `cacheComponents: true` does nothing for `pages/` routes. If the project has a `pages/` or `src/pages/` tree but no `app/` or `src/app/` tree, stop and tell the user — Pages → App migration is its own project, not part of this skill. A hybrid app (both `pages/` and `app/`) is fine: the flag affects the `app/` routes; `pages/` routes are unaffected and don't need opt-outs.
- **Next.js 16.3 or later.** That release is where the pieces this skill relies on land: top-level `cacheComponents`, `export const instant`, the dev-overlay instant-navigation validation warnings, and the `cache-components-instant-false` codemod. If `next --version` reports below 16.3, upgrade first:
-`npx @next/codemod@latest upgrade latest` to apply the version-to-version codemods.
- Read the relevant [version upgrade guide](https://nextjs.org/docs/app/guides/upgrading) (e.g. [Version 16](https://nextjs.org/docs/app/guides/upgrading/version-16)) for what the codemod doesn't cover.
- **No incompatible config keys.** `cacheComponents: true` errors on any file that still exports `dynamic`, `revalidate`, or `fetchCache`. **Translate, don't delete.** Each export encodes behavior the route needs to keep doing; migrate each one to its Cache Components equivalent via the [migration guide's per-key sections](https://nextjs.org/docs/app/guides/migrating-to-cache-components#enable-cache-components). If a value can't be cleanly translated yet, leave a `// TODO: Cache Components adoption — restore revalidate = 3600` comment so the loop picks it up. The `cache-components-instant-false` codemod does not touch these.
- **`experimental.dynamicIO` is fatal.** It was renamed to top-level `cacheComponents` and the old key now aborts before any build can run — remove it (or replace with `cacheComponents: true`) first. `experimental.useCache` is still accepted as a deprecated alias; redundant once `cacheComponents: true` is set, so remove it for clarity.
### notes
- **No passing baseline before the flag.** If the app already uses `"use cache"`, the pre-flag build errors with `please enable the feature flag cacheComponents`. Enabling the flag is the first thing you do (in Incremental, before the codemod; in Direct, before fixing routes) — not a thing to do _after_ getting a passing build. Note this in your starting summary so it doesn't read as a regression.
- **Offline docs.** Guide links have offline copies under `node_modules/next/dist/docs/` (bundled since Next.js 16.2), with the directory layout numbered for ordering (e.g. `node_modules/next/dist/docs/01-app/02-guides/migrating-to-cache-components.md`). If you can't predict the numbered prefix, `find node_modules/next/dist/docs -name '<slug>.md'` resolves it. The `/docs/messages/*` error pages are not bundled.
- **Older versions without bundled docs.** Suggest `npx @next/codemod@latest agents-md` to the user before starting: it downloads a version-matched copy to `.next-docs/` and writes an index into `AGENTS.md` / `CLAUDE.md`. It touches files in their repo, so ask first and run it only if they want it.
## the shape of the work
There's one loop: walk the route tree top-down, one feature at a time, adopting each route against `next dev` + a browser. The build is a final check for each feature, not the working surface.
The choice in step 1 is whether to opt every route out of validation first or fix routes as you go. Either way the loop is the same:
- **With a quiet pre-step (Incremental).** Run the codemod to opt every page and layout out of validation. Once you've also fixed what the codemod can't (sync-IO calls, leftover `revalidate`/`dynamic`/`fetchCache` exports), the build passes; you ship that as its own PR and then start the loop — removing one opt-out at a time and adopting that route. This splits the work into small, reviewable PRs.
- **Without (Direct).** Enable `cacheComponents` and start the loop on whatever the build flags first. Same loop, but every fix sits on one branch until adoption is complete.
In both, the per-route success bar is the same: **dev loop reports no errors AND `next build` passes**. Check in with the user after every feature. Expect to spend most of the time in the loop, not in the pre-step.
## background
`cacheComponents: true` requires every route to be prerenderable. A route that reads request-time data outside `<Suspense>` is "blocking" and fails the build. `export const instant = false` marks a route as allowed to block, which clears it in both dev and build; on a layout it covers the whole subtree during the build, but client navigations still validate each descendant segment on its own. Reads wrapped in a [`"use cache"`](https://nextjs.org/docs/app/api-reference/directives/use-cache) function count as cache boundaries, not blocking reads.
Three classes of blocker come up, usually in this order:
1.**Request-time reads** (`cookies()`, `headers()`, `await params`, `await searchParams`). All four block when awaited at the top of a page or layout. `params` and `searchParams` often get missed because they're not framed as "request data" the way cookies and headers are. The fix is to push the read into a `<Suspense>`-wrapped child — and for `params`/`searchParams`, forward the promise into the child and await it there; don't `await` at the page top.
2.**Sync-IO at module/render time** (`new Date()`, `Date.now()`, `Math.random()`, `crypto.randomUUID()`). These fail the build even with `instant = false` — the opt-out doesn't suppress them. If they're in a shared layout, they block every route under it. The codemod can't fix them; you have to translate each one by hand before the build can pass (see the [incremental pre-step](#incremental)). Grep the whole repo for these calls before running anything else.
3.**`"use cache"` files that read request data.** A file with a top-level `"use cache"` directive can't export `instant`; combining the two errors with `Only async functions are allowed to be exported in a "use cache" file.`, which means the directive was wrong for that route. Remove it before running the codemod.
## working surfaces
### finding blocking routes
Prefer `next dev` over `next build` while you work.
- **`next dev`** — the working surface. Visit a route; its blocking errors surface in the dev overlay with full stack traces and fix cards linking the per-error docs. Work one route at a time — errors don't accumulate in one place. The route itself still returns HTTP 200, so read the overlay (or `.next-dev.log`), not status codes. A cleared overlay is one half of calling a route clean — the other half is browser verification (see [step 2](#step-2-the-inner-loop-remove-opt-outs-one-feature-at-a-time)) and a passing build for that route.
- **`next build`** — detection only. The build is `next dev`'s authoritative check, not its replacement. Use it as the last gate on each feature in the loop (a passing build is part of the per-route success bar) and as the final verification across the whole app. In Incremental, the build also confirms the pre-step (codemod opted every route out, no shared layout still has a sync-IO blocker) before you ship that PR. Don't reach for the build instead of the dev loop while you're working a route — a passing compile doesn't tell you what ended up in the static shell and what streamed. By default the build stops at the first blocking route, so it's also poor for sizing the work. Two flags help when iterating: `--debug-build-paths` builds only the routes you name (comma-separated glob patterns of file paths relative to the project root, e.g. `--debug-build-paths="app/admin/**/page.tsx"` — not URL paths; `--debug-build-paths="app/(marketing)/about/page.tsx"` — not `/about`; `--debug-build-paths="app/admin"` matches nothing and silently builds zero routes), and `--debug-prerender` disables the early exit so the build continues past the first prerender failure, reports every blocking route, and prints a fuller stack trace that names the originating file and line.
Every blocking error has a docs page — open it. Both the dev overlay and the build terminal print a `https://nextjs.org/docs/messages/<slug>` link with each error. That page is the canonical recipe for the fix; the inline message is a summary. Fetch the link for every distinct error you encounter, even if you think you know the pattern — the recipes evolve, and the same error class can have different correct fixes depending on what the route reads. Don't improvise from the inline message alone. (`/docs/messages/*` pages aren't bundled offline; if you have no network, fall back to the per-API guides under `node_modules/next/dist/docs/` and note the limitation when you report back.)
### verifying each fix at runtime
A passing build or a cleared overlay isn't proof the route actually behaves — Cache Components is a runtime concern (a static shell with streamed data). Verify after every fix, not only at the end.
In preference order:
1.**[`next-dev-loop`](https://github.com/vercel/next.js/tree/canary/skills/next-dev-loop) — strongly preferred.** Cross-checks `/_next/mcp` against the live browser via `agent-browser` and surfaces both compile and runtime issues in one pass. The diagnostics (React tree, suspense boundaries, console + network) are richer than poking at `next dev` by hand.
Install it before starting the loop. Don't wait until you hit something `next dev` alone can't explain. Run:
The skill requires `agent-browser >= 0.27.0` and walks you through it.
**Requires Turbopack.** If `package.json`'s `dev` script passes `--webpack`, flag it to the user and ask whether there's a reason to stay on webpack. If not, switch to Turbopack (the Next.js 16.3+ default). If they want to keep webpack, skip this install and use the [build-only loop](#the-loop-build-only-fallback) instead.
You don't need permission to install `next-dev-loop` itself. It's a tool, like installing a dev dependency. If a user is present, briefly tell them you're installing it for verification. In a non-interactive run (CI, dashboard, sandbox), install it without asking — "can't prompt the user" is not a reason to skip. The only legitimate skip is a real technical blocker: no network, no npm, read-only filesystem, a stated no-new-deps policy, or a webpack-only dev script. If you skip, name the specific blocker in your final report.
2. **A browser you can drive yourself.** Playwright, `agent-browser` directly, any browser-automation tool. Use only when `next-dev-loop` is genuinely blocked. You'll miss the framework-side checks (`/_next/mcp`), so DOM assertions alone don't catch every regression — be more cautious about what you call "verified."
3. **Build-only.** If you can't run a dev server at all, the build is your only signal. `○ (Static)` routes with no `<Suspense>` are fully verified by the build (nothing streamed to test). `◐ (Partial Prerender)` routes are only shell-verified — flag them when you report back.
4. **No tooling at all.** Ask the user to run the dev server (or build) and report what they see, or commit the milestone you've reached and hand off.
## step 1: choose a strategy
Ask the user, in terms of the PRs they want, not the size of the job. Never use the internal labels (Incremental, Direct) when talking to the user — those are your own scaffolding. Ask in terms of PRs and features, e.g.: _"Do you want me to first open a PR that turns on Cache Components and opts every route out of validation, then handle the actual route adoptions feature-by-feature in follow-up PRs? Or do everything on one branch?"_ Even on a tiny app, the incremental path still has value (review-sized PR, revertible, the `// TODO: Cache Components adoption` markers double as your work queue for next session). Don't pick on their behalf.
If there's no user to ask, default to **Incremental** and document the choice.
- **Incremental** — quiet pre-step + the loop. Run the codemod to opt every page and layout out of validation, get the build passing, stop and check in with the user (see [end of the pre-step](#end-of-the-pre-step-check-in)), then enter [step 2's loop](#step-2-the-inner-loop-remove-opt-outs-one-feature-at-a-time) and ship each feature as a follow-up PR.
- **Direct** — skip the pre-step. Enable `cacheComponents` and go straight to [step 2's loop](#step-2-the-inner-loop-remove-opt-outs-one-feature-at-a-time); the build's blocking routes are the work queue.
### incremental
Before invoking the codemod, fix the two classes of blocker it can't.
1. **Sync-IO at module/render time.** Grep the whole repo for `new Date()`, `Date.now()`, `Math.random()`, and `crypto.randomUUID()` (not only `app/**/layout.{js,jsx,ts,tsx}` — the read might live in any component imported by a layout). Unblock each match with the `await connection()` + `<Suspense>` fix from its `blocking-prerender-*` error card: it defers the value to request time, exactly as it behaved before the migration, so it needs no product decision. Add this exact comment on the line above the `await connection()`:
```tsx
// TODO: Cache Components adoption. Added to unblock the build: remove this connection() to re-trigger the error and review the fix options.
```
It shares the `TODO: Cache Components adoption` prefix with the comments the codemod writes, so the check-in grep finds both. Removing the `await connection()` makes the error fire again with its fix cards — the same motion as removing an opt-out in the loop.
2. **Incompatible segment configs.** Grep for `^export const (revalidate|dynamic|fetchCache)` across `app/` and translate per the `requires` note above. The codemod does not touch them; leaving them in place fails the build after the codemod.
The codemod refuses to run on a dirty working tree. Commit or stash unrelated work first, or pass `--force` to let its edits land alongside your WIP. Common false positive: if you recently upgraded Next.js, `package.json` and the lockfile will already be dirty — commit those first.
Use the `@canary` channel, not `@latest`. The `cache-components-instant-false` transform isn't in the stable `@next/codemod` release; `@next/codemod@latest` errors with `Invalid transform choice`.
Inserts `export const instant = false` (with a `// TODO: Cache Components adoption` comment) into every `app/**/{page,layout,default}` file, skipping files that already declare `instant` and any module marked `"use client"` or `"use server"`. Then set `cacheComponents: true`. The TODO comments are the work queue for the loop.
If the codemod isn't available (older `@next/codemod`, sandboxed environment, offline run), reproduce it by hand: for every `app/**/{page,layout,default}.{js,jsx,ts,tsx}` that isn't `"use client"` or `"use server"` and doesn't already declare `instant`, insert this after the imports:
```ts
// TODO: Cache Components adoption. Refactor this route so this opt-out can be removed.
The codemod opts every segment out, not only the root, on purpose. Resolution is top-down, first-explicit-config-wins: the highest `instant = false` decides the whole subtree. With an opt-out on every segment, removing one segment's opt-out validates only that segment; descendants keep their own opt-outs and stay passing. If only the root were opted out, removing it would re-arm validation for the entire app at once.
Because the highest opt-out wins, remove them top-down (root layout first, then descend). Removing a leaf's opt-out does nothing while an ancestor still holds one.
Confirm the pre-step with `next build`. The build is the proof, not the codemod run — a shared layout that calls `new Date()` / `Math.random()` directly still fails regardless of the opt-out (see [background](#background)).
After the build passes, confirm the root layout got an opt-out (`grep -n "export const instant" app/layout.*`). The root layout renders every route, including framework routes like `/_not-found`, so if it was missed, add `export const instant = false` to it by hand.
Synthetic routes like `/_not-found` have no user file — when they block, fix the root layout's opt-out, not the synthetic route. Client Components (`"use client"`) get no opt-out (it's a build error — `E1344` — to export `instant` from them) and rarely block on their own; when a client route blocks, fix the server-side data in its ancestor layout.
### end of the pre-step: check in
Incremental only. Stop here before starting step 2 — the pre-step is the shippable PR. Talk to the user in their language; don't say "Incremental" or other internal labels; talk about adoption, PRs, and what the app does now. Tell them:
- What you did: turned on Cache Components, ran the codemod that opts every page and layout out of the new validation (or did it by hand), fixed any blockers the codemod can't (list them), confirmed the build passes.
- What changed: every page and layout in `app/` now exports `instant = false` with a `// TODO: Cache Components adoption` comment, except client components and any that already had an `instant` export.
- What to sanity-check: the diff is mostly mechanical (new exports + comments). The build passes. Routes still behave exactly as they did before — the opt-outs preserve current behavior; no rendering changes yet.
- The question: "Want to open this as its own PR before we start adopting Cache Components route by route? Or keep going on this branch?" Wait for the answer.
Moving to step 2 without checking in defeats the point of taking the incremental path.
### direct
Set `cacheComponents: true` and move to [step 2](#step-2-the-inner-loop-remove-opt-outs-one-feature-at-a-time). The build's blocking routes are the work queue.
## step 2: the inner loop, remove opt-outs one feature at a time
A "feature" is a single product surface — `app/settings/profile/**`, `app/posts/[slug]/**` — not a whole top-level app like `app/dashboard/**`. Finish one end-to-end before starting the next.
Within a feature, walk top-down (layouts before pages, root layout first). Removing a layout's opt-out before its descendants exposes the layout's own blocking reads. (Direct: there are no opt-outs to remove — fix each failing route; if a hand-written opt-out on an ancestor shadows it, remove that first.)
A passing build mid-walk doesn't mean the layout is clean. Removing a layout's opt-out while its descendant pages still have theirs keeps the build passing — each page shadows the inherited validation. The layout's actual blocking reads only surface once nothing below it shadows them. Don't call a feature done at the layout boundary.
Use the **with-a-browser** loop unless a browser is genuinely unreachable. The [`next-dev-loop`](#verifying-each-fix-at-runtime) skill is the source of truth for what counts as "browser available" and how to install it.
### the loop, with a browser (preferred)
Per route:
- Remove the opt-out (Incremental) or target the failing route (Direct).
- Reload in dev. Overlay clean? Skip to verify. Overlay still red? Fix.
- Fix — fetch the docs page linked from the error (`https://nextjs.org/docs/messages/<slug>`), apply the recipe from there. The inline overlay text is a summary; the docs page is the source of truth.
- Verify in the browser. Confirm the visible content on first paint is what you intended in the shell — not stuck on a fallback, not silently streaming everything out of an empty shell.
- Re-check siblings if the fix touched shared code (a layout, a sidebar component). A shared-shell change can fix the route you're on and break a sibling.
### the loop, build-only (fallback)
Used when there's no way to drive a browser — CI, sandbox, the user has no `next dev` running and you can't start one. Weaker signal: confirms the build passes and the route prerenders, but not what ended up in the static shell vs streamed.
Per route:
- Remove the opt-out (Incremental) or target the failing route (Direct).
- Rebuild with `--debug-build-paths app/<route>/**` (only that route) or `--debug-prerender` (full build, but past the first failure). Route passing? Move on. Still blocking? Fix.
- Fix — fetch the docs page linked from the error (`https://nextjs.org/docs/messages/<slug>`), apply the recipe from there.
- Re-check siblings if the fix touched shared code.
- Flag the route as build-only-verified when you hand the feature off. Each `◐` route still needs a browser pass before the feature is done.
### loop notes
- The [three blocker classes from background](#background) often get missed when fixing in place. Caching a downstream fetch (`getThing(id)`) doesn't clear an `await params` at the top of the page body — push the param promise into the `<Suspense>`-wrapped child.
- Ambiguous calls are user check-ins, not agent judgment. When you're not sure which fix fits, the blocking code looks security-sensitive, or the user might want to keep the route blocking on purpose — read [references/per-page-decisions.md](./references/per-page-decisions.md) before editing. Show the route while you ask: the `next-dev-loop` session runs the browser headed, so drive to the page and leave it on screen so the user is looking at the thing they're deciding about, with a screenshot as the fallback when a headed browser isn't possible. "Should this stay blocking?" is much easier to answer while looking at the page than at a file path.
- Don't narrate the refactor with comments. The only comment the codemod (or you) should leave is `// TODO: Cache Components adoption` on opt-outs, and the user's existing comments. Don't annotate every `<Suspense>` boundary or `"use cache"` call with what it does — the code says that. Drop a comment only when the _why_ isn't clear from the code (e.g. a deliberate Block with a reason).
Keep a todo list of the feature's routes. When every route in the feature is clean, move to step 3.
## step 3: verify the feature
Checklist before checking in with the user:
- `next build` completes without blocking-route errors.
- No bare TODOs in the feature: `grep -rn "TODO: Cache Components adoption"` finds both the codemod's opt-out comments and the sync-IO unblocks from the pre-step. Any `instant = false` left behind is a deliberate, documented Block — comment rewritten to a reason (see [references/per-page-decisions.md](./references/per-page-decisions.md) → "when to leave a Block in place"). Any `await connection()` left behind has been reviewed and kept on purpose, not left over from the pre-step.
- Each route visited in the browser: confirm the static shell renders first and every `<Suspense>` fallback resolves to its real content. Capture both states if you can — the fallback (mid-stream) and the final paint — so you have a streaming-experience demo to show the user. Throttle the network in the browser if streaming is too fast to observe.
Then check in with the user. Same rule as the pre-step: speak their language. Don't say "feature-by-feature loop" or other internal labels; talk about the feature you adopted and what the user will see.
- What you did: which routes you touched, and the user-visible result per route (e.g. "the post page now streams the article body behind a skeleton while the layout stays static").
- What changed: opt-outs removed, fallbacks added, caching boundaries introduced.
- Show, don't tell. The `next-dev-loop` session runs the browser headed, so drive the route live for the user so they see the static shell → fallback → final content sequence in real time. If you can't drive a live browser, attach the before/after screenshots you captured instead.
- Give them the click-through: a short table of the feature's routes — the URL to open and what to look for (what renders instantly, which fallbacks appear, what streams in) — so they can verify each one themselves.
- The question: "Want to open this feature as a PR and move on to the next, or stop here?" Wait for the answer.
**Trivial features can skip the check-in.** If adopting a feature only meant removing its `// TODO: Cache Components adoption` opt-out (no `<Suspense>` added, no `'use cache'` introduced, no render order change), the user sees nothing different. Move on to the next feature without stopping; mention it in passing the next time you do check in.
When the loop has run on every feature — every remaining `instant = false` sits under a reason comment, `grep -rln "TODO: Cache Components adoption" app` returns nothing — point the user at [further reading](#further-reading) if they want to push the experience further, or stop and ship.
### route table glyphs
`ƒ` → `◐` is where adoption usually lands. `◐ (Partial Prerender)` means a static shell prerenders and the request-time content streams in — the goal state for any route that reads `cookies()`, `headers()`, `params`, or `searchParams`. Some routes legitimately stay `ƒ` when they do request-time work through a documented escape hatch (e.g. a layout that uses `await connection()`); the page is no longer _opted out_, it's genuinely dynamic. Don't remove the escape hatch only to chase a `◐`. The inverse holds: `instant = false` does not force a route to be `ƒ`. The glyph reflects what the route does at prerender time, not which validation knobs it exports.
`◐` tells you a shell exists, not what's in it. A `<Suspense>` boundary placed too high (e.g. wrapping the entire page body, or `<Suspense fallback={null}>` around the article content) pushes the visible content out of the static shell into the streamed payload; the build still reports `◐` because _some_ shell prerendered (often only `<html><body>` with framework markup). The route table can't tell you what's in the shell; a browser can. If the shell is empty and everything streams, pull the `<Suspense>` boundary down closer to the actual dynamic read.
## further reading
The work below is optional and lives in the docs — link the user to them and let them decide which to take on next. Don't walk these through inside this skill.
- [Instant navigation](https://nextjs.org/docs/app/guides/instant-navigation) — dev-only validation warnings the overlay raises on client navigation. Same shape as the blocking-prerender errors you cleared in step 2; the guide covers the per-warning details. Recommend it next if the user wants navigations to actually be instant (a passing build doesn't guarantee that — a `<Suspense>` above the shared layout caught the page-load case but doesn't cover client navigation).
- [`next-partial-prefetching-adoption`](https://github.com/vercel/next.js/tree/canary/skills/next-partial-prefetching-adoption) — the follow-up skill that adopts Partial Prefetching: it audits `<Link prefetch={true}>` calls (driven by the dev overlay's `link-prefetch-partial` warning) with the flag off first, then flips the `partialPrefetching` config. It sequences this the same way this skill sequences Cache Components, but the insights are dev-only, so it's a browser click-through, not a build loop. Recommended after instant navigation, since those fixes feed directly into how much of each route the shell can prefetch. Concepts live in the [Adopting Partial Prefetching guide](https://nextjs.org/docs/app/guides/adopting-partial-prefetching).
- [Prefetching](https://nextjs.org/docs/app/guides/prefetching) and [Runtime prefetching](https://nextjs.org/docs/app/guides/runtime-prefetching) — broader prefetching reference. Runtime prefetching extends the static shell with per-session content; reach for it when a route's shell is too thin to be useful and Partial Prefetching alone doesn't cover the gap.
- [Prevent regressions with e2e tests](https://nextjs.org/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests) — the `@next/playwright` [`instant()`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config/instant#testing-instant-navigation) helper asserts on the UI that's available immediately on navigation, so regressions surface in CI. Recommend it once a route is instant: `next-dev-loop` confirms it _now_; an `instant()` test keeps it that way.
- [`next-cache-components-optimizer`](https://github.com/vercel/next.js/tree/canary/skills/next-cache-components-optimizer) — a separate skill that grows each route's static shell so more of the page prerenders and less streams in. Pure optimization, not part of adoption.
Read this when a route still blocks after you remove its `instant = false` and the dev overlay's fix card isn't enough on its own. Each section here covers a judgment call the agent shouldn't make alone.
## deciding what to do with a blocking read
Read the full linked page behind the fix card — not only the inline snippet — before editing. The card unblocks the build, but the page covers the details that make the route's navigation actually instant (e.g. where to place a `<Suspense>` boundary). Don't improvise.
If you're unsure which fix fits, the right call usually depends on what this part of the page is _for_, which the code doesn't capture. Ask the user about their goal for it rather than guessing. Frame it as a product/UX question: should this content be there instantly on load, or is it fine for it to stream in a moment later? Should everyone see the same thing (cacheable) or is it per-user / per-request? Tie the technical fix to that answer (cache it, wrap it in `<Suspense>`, or keep it request-time), so they're deciding the experience, not the API.
## security gates and other code you can't infer
If the blocking code looks like it's there for a _reason you can't infer_ — a security gate at the page top (`await verifyAccess()`, an auth redirect, a feature-flag check) where moving it inside `<Suspense>` would change what the code guarantees — stop and ask the user before refactoring. The build error wants `<Suspense>`, but wrapping a gate in `<Suspense>` defeats the gate. Only the person who wrote it knows whether to keep the route blocking (`instant = false` as a documented Block), restructure the page so the gate runs differently, or move the check to [Proxy](https://nextjs.org/docs/app/api-reference/file-conventions/proxy).
If _every_ route under a layout is gated this way, a documented Block on the layout is the correct end state. Moving the gate to [Proxy](https://nextjs.org/docs/app/api-reference/file-conventions/proxy) is the architectural fix, not a Cache Components one, and that's a follow-up rather than something to hold the migration on.
For the broader picture, read the [Authentication guide](https://nextjs.org/docs/app/guides/authentication) (where auth checks belong: Proxy for routing, Data Access Layer for data) and the [Data Access Layer section of Data Security](https://nextjs.org/docs/app/guides/data-security#data-access-layer) (centralized auth checks that compose with `'use cache'`).
If you don't know how to make a piece of code Cache Components–correct without changing what it does, ask.
## when to leave a Block in place
If a route is genuinely meant to block — it's inherently per-request with no useful static shell — or the refactor would be large and the user would rather not take it on now, that's a legitimate outcome. Keep `instant = false`, but confirm it with the user first and turn its `// TODO: Cache Components adoption` comment into a reason, e.g. `// instant = false: kept on purpose — fully request-time dashboard` or `// instant = false: deferred, refactor too large for now`.
A documented, deliberate Block is fine to leave after the migration; an undocumented leftover opt-out is not.
# Refactor patterns — push dynamic down into the shell
Each pattern is **before → after**: keep as much as possible in the prerendered shell, and wrap only genuinely per-request work in a tight `<Suspense>` (or hoist it into `use cache`). Production shapes — parallel-route slots, deferring an auth gate, client slot-routers — are in `real-app-patterns.md`.
---
## 1. Awaiting at the top → move the await into a Suspense child
The most common blocking shape. Awaiting request-time data at the top of a page/layout makes **everything below it** dynamic.
```tsx
// ❌ before — top-level await of a non-static param + uncached data
Decide per data source. Same-for-everyone & rarely-changing → cache it (it joins the shell). Per-request & must-be-fresh → leave it uncached behind a boundary.
constinventory=awaitdb.inventory.findBySlug(slug);// must be fresh
```
```tsx
// ✅ after — cache the stable one (shell), defer the fresh one (streams)
asyncfunctiongetProduct(slug: string){
"use cache";// → resolved at prerender, lands in the shell
returndb.products.findBySlug(slug);
}
<Suspensefallback={<p>Checkingavailability…</p>}>
<Inventoryparams={params}/>{/* uncached read stays here, streams in */}
</Suspense>;
```
> A bare `'use cache'` applies the `default` `cacheLife` profile. Choose freshness explicitly with `cacheLife('<profile>')` (`default` / `seconds` / `minutes` / `hours` / `days` / `weeks` / `max`) rather than shipping the default lifetime by omission.
>
> Serverless note: `use cache` is in-memory and does not persist across instances — use [`use cache: remote`](https://nextjs.org/docs/app/api-reference/directives/use-cache-remote) for a durable shell.
**Insight:** [uncached data during prerendering](https://nextjs.org/docs/messages/blocking-prerender-dynamic).
---
## 4. Dynamic params → `generateStaticParams` (shell) or `<Suspense>` (stream)
If the set of params is enumerable, prerender them so `await params` resolves into the shell. Otherwise treat params as request-time and wrap consumers in `<Suspense>`.
```tsx
// ✅ option A — enumerate → params resolve into the shell, no Suspense needed for params
const{slug}=awaitparams;// known at build → shell-safe
// ...
}
```
```tsx
// ✅ option B — not enumerable → params is request-time; await it inside a boundary (pattern #1)
```
Root params (the dynamic segments the root layout sits inside, e.g. `app/[lang]/layout.tsx`) are readable from any Server Component via `next/root-params` without prop-drilling — but under Cache Components they must still be enumerated by `generateStaticParams` (at least one value per root param) to land in the shell, the same as any other dynamic param.
**Insight:** [runtime data during prerendering](https://nextjs.org/docs/messages/blocking-prerender-runtime).
---
## 5. `searchParams` → always behind `<Suspense>` (on page load)
Search params are never known at build, so awaiting them (or `useSearchParams()`) suspends on a page load. Keep the rest of the page in the shell by isolating the consumer.
```tsx
// ✅ static content stays in the shell; the search-dependent part streams
On a **client navigation** the router already has the URL, so a `useSearchParams()` consumer resolves synchronously and can appear in the prefetched shell — but you still need the boundary for the page-load path.
**Insight:** [runtime data during prerendering](https://nextjs.org/docs/messages/blocking-prerender-runtime) (or, via `useSearchParams` in a Client Component, [URL data in a Client Component](https://nextjs.org/docs/messages/blocking-prerender-client-hook)).
---
## 6. Non-deterministic values → `connection()` + `<Suspense>`, or cache
`Math.random()`, `Date.now()`, `crypto.randomUUID()` produce different output each run, so Cache Components makes you choose: per-request (defer) or fixed (cache).
```tsx
// ✅ per-request value: gate on connection() and wrap in Suspense
// ✅ same value for everyone: cache it so it joins the shell
asyncfunctionbuildId() {
"use cache";
returnDate.now();
}
```
**Insight:** [`Date.now()`](https://nextjs.org/docs/messages/blocking-prerender-current-time), [`Math.random()`](https://nextjs.org/docs/messages/blocking-prerender-random), or [`crypto`](https://nextjs.org/docs/messages/blocking-prerender-crypto) while prerendering.
---
## 7. Dynamic `generateMetadata` → static export, `use cache`, or a dynamic-marker for runtime data
```tsx
// ❌ before — reading request data blocks the route's metadata
exportasyncfunctiongenerateMetadata() {
constc=awaitcookies();
return{title: c.get("title")?.value};
}
```
```tsx
// ✅ option A — static
exportconstmetadata={title:"Store"};
// ✅ option B — cache the metadata (depends on external data, not runtime data)
exportasyncfunctiongenerateMetadata() {
"use cache";
return{title: awaitgetTitle()};
}
```
```tsx
// ✅ option C — metadata genuinely needs runtime data (cookies/headers):
// keep generateMetadata dynamic, and add a dynamic-marker component to the
// page so the rest of the page still prerenders into the shell.
<article>{/* static content — stays in the shell */}</article>
<Suspense>
<DynamicMarker/>
</Suspense>
</>
);
}
```
`generateViewport` is the same, except dynamic viewport blocks the **whole page**. Genuine instant fixes: a static `viewport` export, or `use cache`. The other two are dynamic-acceptance opt-outs, not instant fixes — do not treat them as a way to reach GREEN: `export const instant = false` opts the segment out of validation while the navigation still blocks, and a `<Suspense>` above the document `<body>` makes the whole route dynamic.
**Insight:** [runtime data in `generateMetadata()`](https://nextjs.org/docs/messages/blocking-prerender-metadata-runtime).
---
## 8. Keep the LCP element in the shell
Don't bury the main heading (the LCP element) inside a boundary — it can't paint until the boundary resolves.
```tsx
// ✅ LCP outside the boundary → paints in the shell
<h1>{product.name}</h1>{/* shell (cache the name if needed) */}
A single boundary in the **root** layout passes a page-load check but leaves sibling client navigations blocking. Put a boundary **below the shared layout**.
```tsx
// app/store/layout.tsx — boundary below the /store shared layout covers
// client navs like /store/shoes → /store/hats (the root boundary does not)
Prefer per-component boundaries inside the page (patterns #1–#5) over one big layout boundary — they keep more real content in the shell and stream independently.
**Insight:** the read's own insight surfaces on the client navigation when the boundary is too high — see [where to place the boundary](https://nextjs.org/docs/messages/blocking-prerender-dynamic#choosing-where-to-place-the-boundary).
## 10. Can't push the read down? Runtime-prefetch the whole route
Patterns 1–9 grow a **static shell** by moving dynamic reads behind boundaries. Some routes resist that: an ID minted per request (`createId()`), an auth/scope resolution the whole subtree needs, a page that is _all_ dynamic by nature. The read can't move, so there is no meaningful shell to commit and the soft nav stays RED. The escape hatch is **runtime prefetching** — don't prerender a shell, run the dynamic render _in the prefetch_ so the whole route is warm before the click and the soft nav commits the real content instantly.
It has **two halves — both required**, and route config alone is RED:
```tsx
// 1. The route opts in — on EVERY leaf (page/default) segment, parallel @slots
// included, or instant validation re-triggers on the segments that lack it
// (the same all-or-nothing coupling as an `instant = false` opt-out).
exportconstprefetch='allow-runtime'
// 2. The <Link> asks for a FULL prefetch — that is what spawns the runtime
// request. A default/auto prefetch only warms the static shell.
<Linkhref={href}prefetch={true}>…</Link>
// <Link prefetch> already issues the full prefetch on hover and on viewport
// entry, so prefer it. An imperative full prefetch via router.prefetch needs
// the non-exported PrefetchKind enum, so it has no clean public form.
```
Under `instant()` the runtime entry is what commits, so the real content (not a skeleton) shows under the lock — that is the GREEN.
Gotchas (each cost real debugging time):
- **The full prefetch is mandatory.** With App Shells enabled an auto/PPR prefetch bails before the runtime spawn (`subtreeHasSpeculativePrefetch`); only `prefetch={true}` / `kind: 'full'` reaches it. If you set `prefetch = 'allow-runtime'` and it's still RED, the link is doing an auto prefetch.
- **All leaf slots must agree.** `allow-runtime` on the content segment but `instant = false` (or nothing) on a sibling `@header`/`@sidebar` leaf leaves the route's runtime entry incomplete, so the lock falls back to the shell. Flip every leaf together.
- **Prefetch the canonical URL.** A link whose href 307-redirects (a `/foo` that canonicalizes to `/`) can't be prefetched — the prefetch receives the redirect, not the tree. Point the link and the prefetch at the final URL.
- **Don't blanket the full prefetch.** It fetches _all_ the target's dynamic data; issuing it on hover for every link (recents that point at whole chats) is wasteful. Scope `kind: 'full'` to the runtime-prefetch targets only.
- **Marker must be a committed node, not RSC bytes.** The content is often a client component, so its text isn't in the prefetch response — assert a `data-testid` that renders when the client subtree commits, not a substring of the stream.
Prefer a static shell (patterns 1–9) whenever the read can move: it's cheaper than a runtime prefetch and also covers hard load. Reach for runtime prefetching when the read genuinely can't move, or for a route that's all-dynamic by design.
**Insight:** [dynamic data during prefetching](https://nextjs.org/docs/messages/instant-link-prefetch-partial).
The rest of this skill models a single linear `layout → page` tree. Production App Router routes add **parallel routes, shared layout UI, and auth gates**, which is where most of the real static-shell work happens. These patterns bridge that gap. Read the skill's `SKILL.md` first.
## Parallel routes: each slot is its own boundary
Instant validation treats every parallel-route slot below the shared layout as an **independent** navigation boundary. Consequences:
- **Each `@slot` needs its own `<Suspense>`** around its dynamic reads; a boundary in one slot does not cover another.
- **An uncovered dynamic read in any slot blocks the whole navigation.** A perfect `@content` does not help if `@sidebar` awaits a session at the top.
- **A slot that renders `null` (e.g. `default.tsx`) is shell-safe**: it is static and performs no reads. Slots that do not re-render for this navigation cost nothing.
```
[tenant]/layout.tsx (shared: already mounted on a soft navigation; not re-rendered)
├ @sidebar → side nav ← …here too (independent boundary)
└ @header → default.tsx → null ← free
```
## Client-rendered slot routing is not part of the soft-navigation re-render
A common pattern: a stable shared layout renders `@header`/`@sidebar` through a **client** component that swaps slot content based on `usePathname()`. On a soft navigation, Next.js only re-renders the **server** segments that changed below the shared layout; a client-component subtree is not part of that re-render. So that navigation UI neither blocks the navigation nor needs a server `<Suspense>` for it; only the server segments that actually change (e.g. `@content`) matter. It does participate in an initial load (see the caveat below).
## "Instant" is not "useful shell": the empty-shell failure mode
Validation checks that a dynamic read is **guarded by a boundary**, not that the fallback is non-empty. A `<Suspense>` with no `fallback` (or `fallback={null}`) passes validation and commits instantly, but renders a **blank** shell. If a layout and its page both `await getSession()` (your auth library's request-time read) at the top under one empty-fallback boundary, the whole frame collapses to nothing while the user waits. "Validates as instant" and "good loading experience" are different goals.
> Give every boundary a real loading skeleton, and place it low so the most real content stays in the shell. A `fallback={null}` directly above `<body>` is a deliberate empty-shell opt-out; an empty fallback lower in the tree is almost always a bug.
## The responsive-skeleton mismatch: the shell must match every breakpoint
A loading skeleton that misaligns with the loaded UI is its own bug, and it usually appears on mobile. A hand-built skeleton encodes one layout; the real component is responsive and changes shape at breakpoints, so a desktop-shaped skeleton no longer lines up once the viewport is small.
A concrete shape: a list-detail view renders a list or tree in a side panel on desktop, but collapses that panel into a single dropdown or drawer on mobile (with its own loading state). A row skeleton built for the desktop panel has nothing to align with on mobile.
The fix is the same push-down as everywhere else: **share the real responsive layout between the live render and the shell render.** One responsive component renders both (its data slots show the reused `*Skeleton` in the shell and real data after the stream), so the breakpoint switch happens once, for both renders, and there is no second desktop-only skeleton to drift.
(Same hoist rule, responsive layout included.) Verify the shell at both desktop and mobile widths against the real render at the same width.
## Deferring an auth gate / top-level `await` in a layout
A top-level `await` in a layout blocks everything below it (the most common blocker; see [Runtime data during prerendering](https://nextjs.org/docs/messages/blocking-prerender-runtime)). Auth gates are the most common real instance:
```tsx
// ❌ Before: the await + redirect at the top blocks the whole settings frame
constsession=awaitgetSession();// your auth library's request-time read; suspends during prerender → frame can't build
if(!session?.user)redirect(getLoginUrl());
return<Shell>{children}</Shell>;
}
```
```tsx
// ✅ After: render children unconditionally; move the gate into a Suspense child
import{Suspense}from"react";
exportdefaultfunctionSettingsLayout({children}){
return(
<Shell>
<Suspensefallback={null}>
<AuthGate/>
</Suspense>
{children}
</Shell>
);
}
asyncfunctionAuthGate() {
constsession=awaitgetSession();// the session read suspends during prerender…
if(!session?.user)redirect(getLoginUrl());// …so redirect() never runs at build time
returnnull;
}
```
The shell prerenders as if authorized (the session read suspends before `redirect()` is reached, so the redirect only happens at request time), and `{children}` is now in the shell instead of behind the gate. (`fallback={null}` is correct here: `AuthGate` renders nothing on success.)
## Initial-load shell vs soft-navigation shell
The `test-template.md` specs drive a `<Link>` click for soft navigations and `page.goto()` for initial loads. The two shells can differ for the same route:
> **The initial-load shell can show less than the soft-navigation shell when a layout above the shared boundary awaits un-enumerated `params`/`searchParams`.** An initial load re-runs every layout from the root; if a parent layout does `await props.params` and that segment has no `generateStaticParams`, the param suspends on the initial load and its whole subtree drops out of the shell. A soft navigation does not re-render that parent and already has the params. Symptom: an element present after a `<Link>` click is missing after `goto`.
To assert the soft-navigation shell, drive a real `<Link>` click (through menus if necessary). Use `page.goto()` inside `instant()` to assert the initial-load shell, or when no parent above the shared boundary awaits un-enumerated params, in which case the two coincide.
## Edge cases
- **A `React.cache` (or custom memoization) wrapper around `cookies()`/`headers()` still suspends.** Memoizing the call does not make it shell-safe: the underlying request read still returns a pending promise during prerender. Only the **`use cache`** directive, keyed on static or param inputs, puts data in the shell.
- **Playwright cannot see a `display: contents` or fragment fallback.** Such a fallback reads as hidden, so `instant()` assertions cannot `toBeVisible()` it. Give fallbacks a real wrapper element with a `data-testid`.
| **Selector matches nothing** | a guessed `getByRole('button', { name: 'Folder' })` | grep the component for the real accessible name; add a `data-testid` |
| **Conditional redirect** | the route `redirect()`s for the test user (flag/role), so the marker page is never reached | check the page's top-level branches; assert at the real destination, or pin the flag |
| **Flag / plan / role gate** | the author has the flag or plan; the test user does not | run the unlocked baseline as the test user; pin flags via the project's override mechanism |
| **Empty state** | the marker only exists when data does; the CI account is empty | pick a marker present in the empty state (a layout element such as the page header), or seed data |
| **Timeout / flake** | a slow API or transient infrastructure error | re-run; separate infrastructure flake from a real signal |
| **Streamed marker** | the marker is behind `<Suspense>`, so it is never in the shell | choose a sync shell element; verify it sits outside every `<Suspense>` |
| **Auth redirect** | unauthenticated → `/login` | confirm login succeeded before the navigation |
| **Stale deployment** | the test ran against the previous build (the URL under test still serves the prior deploy) | poll the deployment for a marker from the latest commit before trusting any verdict |
| **Hidden / off-screen** | the testid is on a hover-overlay or off-screen list item | put the marker on an always-visible node; `.filter({ visible: true }).first()` for lists |
## Worked cases
These are illustrative failures from real optimization runs; each was red for a wrong reason, and
none was an instant-navigation problem. One app's drift surface might be dominated by feature flags and
plans; another's by auth state, an empty database, or locale. The taxonomy lists every wrong
reason; the rig file's DRIFT list says which rows apply to your app.
- **Guessed selector + empty state**: the marker was a button picked by a guessed accessible name
that no element actually had, on a list page whose CI account had no rows. → checks 7, 9. Fix: a
`data-testid` on a real static-shell node.
- **Hidden marker**: the testid sat first on a `hidden sm:block` hover-overlay link, then on an
off-screen carousel card; Playwright resolved the element but reported it hidden. → check 10.
Fix: an always-visible node; for lists, `.filter({ visible: true }).first()`.
## Differential check (capture in the PR)
The strongest evidence that the RED measured the property:
```
1. on the fixed branch → GREEN
2. revert ONLY the fix (the <Suspense> push-down) → RED
3. re-apply → GREEN
4. confirm no other change moves it
```
Link the two runs (or include the toggle diff and results) in the PR description. A reviewer who
sees the differential knows the test measures the property.
## `instant()` is not a stopwatch
The test does not measure how fast a navigation is. `instant()` gates dynamic data so the content
of the static shell can be asserted; the signal is presence, not speed. Under the lock, an instant
route's shell is present, and a blocking route's content never commits, regardless of wait time.
Therefore:
- The shipped assertion is `await expect(SHELL_MARKER).toBeVisible()` under the lock. Do not add a
custom timeout or a `painted` boolean.
- A custom short timeout (e.g. `3000`) implies a race against a clock that does not exist. It adds
nothing to the verdict and invites false REDs on an instant route whose commit lands a microtask
late.
- Do not use `locator.isVisible({ timeout })` as a soft wait: Playwright deprecated and ignores
that timeout; the call returns immediately.
- "Renders for the test user" (checks 7-9) is established at authoring time with the unlocked
baseline scaffold, not by a timed assertion in the shipped test.
## `instant()` guards need no retries and no prefetch warming
An `instant()` guard is deterministic. Do not configure retries on one, and do not hover-warm to
help a prefetch land in time. Under the lock, the router initiates the route prefetch and awaits it
before committing (even for a `prefetch={false}` link, even for a route already in the prefetch
cache), so the committed shell does not depend on any prior render, hover, or menu-open prefetch.
A flaky guard has a real cause: a marker that is not a sync node of the destination's shell, a
flag/role/empty-state gap for the test user, or a genuinely blocking route. The fix is in the page or
the marker; a retry masks the regression the guard exists to catch. The only legitimate
`.hover()`/menu-open is when the trigger element itself is not in the DOM until hovered or opened.
## Silent no-op: the testing API must be exposed in the measured build
`instant()` works by setting a cookie (`next-instant-navigation-testing`) that lock code inside the
build reads. It does not throw when that lock code is absent; it only throws on nested calls or an
unknown base URL. If the build was produced without the testing API
(`experimental.exposeTestingApiInProductionBuild`), the cookie is ignored, the navigation runs
normally, and the `instant()` test passes vacuously. A green `instant()` test is only meaningful if
the lock engaged.
Two defenses; use both:
1.**Confirm the API is exposed on the target.** Wire the flag to the platform's preview/staging
condition or an explicit environment variable; the rig file records the project's spelling
(SKILL.md phases 0 and A). Do not trust a pass from a build where it is not set.
2.**Make the test self-validating**: for any route with deferred content, also assert that the
deferred content is gated under the lock, not only that the shell is present
(`test-template.md`, self-validating variant). If the lock did not engage, the content is
already present and `toHaveCount(0)` fails.
The gated half holds under the lock for both navigation types regardless of warm state: the
soft-nav client lock gates dynamic-data writes, and on an initial load the server honors the
cookie on the document request and suspends dynamic data. A vacuous pass is only possible with
a build produced WITHOUT the testing API, which defense #1 above covers.
## Determinism and the rig
- Always measure on a production build, never `next dev`; SKILL.md phase A owns this invariant and
its rationale.
- Run the RED several times; an intermittently red gate is not a gate. If it flakes, determine
whether the cause is infrastructure (transient errors) or a real race before trusting either
description:Sanity development best practices for schema design, GROQ queries, TypeGen, Visual Editing, images, Portable Text, Studio structure, localization, migrations, Sanity Functions, Blueprints, and framework integrations such as Next.js, Nuxt, Astro, Remix, SvelteKit, Angular, Hydrogen, and the App SDK. Use this skill whenever working with Sanity schemas, defineType or defineField, GROQ or defineQuery, content modeling, Presentation or preview setups, Sanity-powered frontend integrations, Sanity Functions, documentEventHandler, defineDocumentFunction, defineMediaLibraryAssetFunction, @sanity/functions, @sanity/blueprints, sanity.blueprint.ts, event-driven content automation, or when reviewing and fixing a Sanity codebase.
---
# Sanity Best Practices
Comprehensive best practices and integration guides for Sanity development, maintained by Sanity. Use the quick reference below to load only the one or two topic files that match the task.
## When to Apply
Reference these guidelines when:
- Setting up a new Sanity project or onboarding
- Integrating Sanity with a frontend framework (Next.js, Nuxt, Astro, Remix, SvelteKit, Hydrogen)
- Writing GROQ queries or optimizing performance
- Designing content schemas
- Implementing Visual Editing and live preview
- Working with images, Portable Text, or page builders
- Configuring Sanity Studio structure
- Setting up TypeGen for type safety
- Implementing localization
- Migrating content from other systems
- Building custom apps with the Sanity App SDK
- Managing infrastructure with Blueprints
- Automating content workflows with Sanity Functions
## Global Rules
- Let Sanity generate `_id` values for ordinary documents. Do not create deterministic UUIDs, slug-derived IDs, or legacy-system IDs when creating documents.
- Model relationships with `reference` fields, then resolve related documents with GROQ lookups, source-key fields, or returned `_id` values from created documents.
- Use explicit document IDs mainly for singleton documents controlled by Studio Structure, including localized singletons such as `homePage-en`.
## Quick Reference
### Integration Guides
-`get-started` - Interactive onboarding for new Sanity projects
-`nextjs` - Next.js App Router, Live Content API, standalone Studio
-`nuxt` - Nuxt integration with @nuxtjs/sanity
-`angular` - Angular integration with @sanity/client, signals, resource API
-`astro` - Astro integration with @sanity/astro
-`remix` - React Router / Remix integration
-`svelte` - SvelteKit integration with @sanity/svelte-loader
-`hydrogen` - Shopify Hydrogen with Sanity
-`project-structure` - Standalone Studio and monorepo patterns
-`app-sdk` - Custom applications with Sanity App SDK
-`blueprints` - Infrastructure as Code: blueprint files, stacks, plan/deploy workflow, error recovery, CI deploys
-`functions` - Automating content workflows with Sanity Functions
### Topic Guides
-`groq` - GROQ query patterns, type safety, performance optimization
-`schema` - Schema design, field definitions, validation, deprecation patterns
-`visual-editing` - Presentation Tool, Stega, overlays, live preview
-`page-builder` - Page Builder arrays, block components, live editing
-`portable-text` - Rich text rendering and custom components
-`typegen` - TypeGen configuration, workflow, type utilities
-`seo` - Metadata, sitemaps, Open Graph, JSON-LD
-`localization` - i18n patterns, document vs field-level, locale management
-`migration` - Content import overview (see also `migration-html-import`)
-`migration-html-import` - HTML to Portable Text with @portabletext/block-tools
## How to Use
Start with the single framework or topic guide that best matches the request, then read additional references only when the task crosses concerns. Use these reference files for detailed explanations and code examples:
description:Integration guide for Angular, including @sanity/client setup, data fetching with signals and resource API, Portable Text rendering, and image optimization.
---
# Angular & Sanity Integration Rules
Jump to the section that matches your Angular version or integration task instead of reading this guide straight through.
## Table of Contents
- Setup and configuration
- Client setup (service pattern)
- Data fetching patterns
- Routing
- Portable Text rendering
- Image optimization
- Modern Angular features
- SSR and prerendering
- Visual Editing
- Error handling
## 1. Setup & Configuration
Use the official template `sanity-template-angular-clean` as a starting point. It provides a monorepo structure:
Configure environment files for Sanity credentials:
```typescript
// environments/environment.ts
exportconstenvironment={
production: false,
sanity:{
projectId:"your-project-id",
dataset:"production",
apiVersion:"2025-05-01",
},
};
```
```typescript
// environments/environment.production.ts
exportconstenvironment={
production: true,
sanity:{
projectId:"your-project-id",
dataset:"production",
apiVersion:"2025-05-01",
},
};
```
> There is no Angular-specific Sanity SDK. Use `@sanity/client` directly, wrapped in an Angular service.
### TypeGen in a Monorepo
Sanity TypeGen generates TypeScript types from your schema and GROQ queries. In the Angular monorepo template, TypeGen runs from the Studio side but scans your Angular app's source files. Ensure `studio/sanity.cli.ts` points at the Angular app:
```typescript
// studio/sanity.cli.ts
import{defineCliConfig}from"sanity/cli";
exportdefaultdefineCliConfig({
typegen:{
enabled: true,
path:"../angular-app/src/**/*.ts",
generates:"../angular-app/sanity.types.ts",
},
});
```
The remaining defaults (`overloadClientMethods: true`, `schema: "schema.json"`) work as-is. Include the generated types file in `angular-app/tsconfig.json` (usually covered by `"include": ["src/**/*.ts", "sanity.types.ts"]`). See `typegen.md` for the full TypeGen workflow, git strategy, and configuration options.
## 2. Client Setup (Service Pattern)
Create an injectable service wrapping `@sanity/client` and `@sanity/image-url`:
For preview/draft content, create a second client instance with a token and `useCdn: false`. Never expose tokens in client-side bundles — use server-side rendering or a proxy endpoint for authenticated requests.
## 3. Data Fetching Patterns
### A. `resource` API (Angular 19+, Recommended)
The `resource` API works natively with promises and integrates with Angular signals:
The `resource` automatically re-fetches when `slug` changes and exposes `value()`, `isLoading()`, and `error()` signals.
> **TypeGen:** Wrapping queries in `defineQuery` enables Sanity TypeGen to infer return types automatically — no manual type imports needed. See `typegen.md` for the full workflow.
### B. `rxResource` (Observable-based)
For teams using RxJS patterns or needing operators like `retry` and `debounceTime`:
With `withComponentInputBinding()`, the `:slug` route param is automatically bound to `slug = input.required<string>()` on the component — no need to inject `ActivatedRoute`.
## 5. Portable Text Rendering
### A. `@portabletext/to-html` with Angular Pipe (Recommended)
### B. `@limitless-angular/sanity` (Community, Component-based)
For full Angular component control over each block type, the community library `@limitless-angular/sanity` provides a component-based Portable Text renderer. This is useful when you need Angular-specific interactivity within rich text blocks.
See `portable-text.md` for Portable Text schema design and serialization rules.
Sanity provides a base64 LQIP (Low Quality Image Placeholder) per image asset — but you must query it explicitly:
```groq
mainImage {
// @sanity/image-url needs these to build URLs with hotspot/crop support
asset,
hotspot,
crop,
alt,
// NgOptimizedImage needs these for placeholder and layout
"lqip": asset->metadata.lqip,
"width": asset->metadata.dimensions.width,
"height": asset->metadata.dimensions.height
}
```
Feed the LQIP directly into `NgOptimizedImage`'s `placeholder` attribute:
```html
<img
[ngSrc]="post.mainImage|sanityImage:1200"
[width]="post.mainImage.width"
[height]="post.mainImage.height"
[placeholder]="post.mainImage.lqip"
[alt]="post.mainImage.alt"
priority
/>
```
Angular applies a CSS blur to the LQIP and crossfades to the full image on load. No extra libraries needed.
> **Note:** LQIP strings are small (~200 bytes) so they're safe to inline in SSR HTML and `TransferState`. See `image.md` for the full image query patterns.
See `image.md` for image field schema patterns and hotspot/crop configuration.
## 7. Modern Angular Features
When building with Sanity, leverage these Angular 19+ features:
- **Standalone components** — Default in Angular 19. No `NgModule` boilerplate needed.
- **Signals and `resource`** — Preferred over RxJS for data fetching. Simpler, less boilerplate.
- **New control flow** — Use `@if`, `@for`, `@switch` with `@empty` for cleaner templates:
- **`inject()` function** — Preferred over constructor injection for cleaner code.
- **Zoneless change detection** — Experimental in Angular 19. Works well with signals-based data fetching since signals automatically notify the framework of changes.
## 8. SSR & Prerendering
Angular 17+ includes built-in SSR support (replacing Angular Universal):
| **Hydration** | `provideClientHydration()` preserves server-rendered DOM. The client reuses it instead of re-rendering. |
| **HTTP Transfer Cache** | Only works with Angular's `HttpClient`. Since `@sanity/client` uses its own HTTP transport, use `TransferState` manually (see below). |
| **Prerendering** | Use `getPrerenderParams` in route config to generate static pages at build time. |
### Transfer State for `@sanity/client`
Angular's built-in HTTP Transfer Cache does not cover `@sanity/client` requests. Without manual transfer, the client re-fetches every query during hydration. Add `TransferState` to the service from Section 2:
The `hashQuery` helper keeps `TransferState` keys short (SHA-256 hex) instead of embedding raw GROQ strings in the serialized HTML.
Prerendering dynamic routes:
```typescript
// app.routes.server.ts
import{RenderMode,ServerRoute}from"@angular/ssr";
exportconstserverRoutes: ServerRoute[]=[
{
path:"post/:slug",
renderMode: RenderMode.Prerender,
asyncgetPrerenderParams() {
// Fetch all slugs from Sanity at build time
constclient=createClient({
projectId:"...",
dataset:"...",
apiVersion:"...",
useCdn: true,
});
constslugs=awaitclient.fetch<string[]>(
`*[_type == "post"].slug.current`,
);
returnslugs.map((slug)=>({slug}));
},
},
{path:"**",renderMode: RenderMode.Server},
];
```
❌ **Bad:** Using `isPlatformBrowser()` in templates to conditionally render content — causes hydration mismatch.
✅ **Good:** Using `@defer` or `afterNextRender()` for browser-only code.
## 9. Visual Editing
> **Important:** Angular does not have official Sanity Visual Editing support. There is no `@sanity/visual-editing` integration, no Stega encoding, and no click-to-edit overlay for Angular applications. This is unlike Next.js, Nuxt, and SvelteKit which have first-party support.
### Preview Mode (Basic)
For draft content preview, create a separate preview client with an API token:
> **Security:** Never expose the preview token in client-side bundles. Use this pattern only with SSR where the token stays on the server, or proxy preview requests through a backend API.
### Community Visual Editing
The community library `@limitless-angular/sanity` provides experimental Visual Editing support for Angular, including overlay click-to-edit functionality. Check its documentation for current status and limitations.
## 10. Error Handling
Common errors when integrating Angular with Sanity:
| `401 Unauthorized` | Invalid or missing API token | Verify token in [Sanity Manage](https://www.sanity.io/manage). Ensure it has correct permissions. |
| `403 Forbidden` | CORS origin not allowed | Add your Angular dev/production URL to CORS origins in [Sanity Manage](https://www.sanity.io/manage). |
| `422 Invalid query` | GROQ syntax error | Test queries in Vision plugin or Sanity's GROQ playground. See `groq.md`. |
| Hydration mismatch | Conditional rendering based on platform | Use `@defer` or `afterNextRender()` instead of `isPlatformBrowser()` checks. |
| Empty response | Missing dataset or wrong `apiVersion` | Verify environment config. Use a date-based `apiVersion` (e.g., `'2025-05-01'`). |
| Images not loading | Missing `@sanity/image-url` setup | Ensure `getImageUrlBuilder` is called with a valid image reference. See `image.md`. |
For GROQ query patterns and best practices, see `groq.md`. For schema design, see `schema.md`.
description:Rules for building custom applications with the Sanity App SDK, including React hooks, document handles, real-time patterns, and Suspense best practices.
---
# Sanity App SDK
Build custom React applications that interact with Sanity content in real-time.
`@sanity/astro` provides the `sanity:client` virtual module. `astro-portabletext` renders Portable Text. `@sanity/image-url` builds image URLs. `groq` exports `defineQuery` for typed queries.
### Configuration (`astro.config.mjs`)
Use the official `@sanity/astro` integration. `astro.config.mjs` runs at config time before Astro's env loading, so `import.meta.env.PUBLIC_*` is not available there — use Vite's `loadEnv` to read the same `PUBLIC_` variables your pages will use.
Astro hoists `getStaticPaths()` into a separate module context. Module-scope `const` declarations in the frontmatter are NOT accessible inside it — referencing them throws `ReferenceError: <NAME> is not defined` at request time. Define queries used by `getStaticPaths` inside the function, or import them from a utility module.
```astro
---
import { sanityClient } from "sanity:client";
import { defineQuery } from "groq";
import { PortableText } from "astro-portabletext";
// Module-scope queries are fine for module-scope code…
import { PortableText } from "astro-portabletext";
const { body } = Astro.props;
---
<div class="prose">
<PortableText value={body} />
</div>
```
## 4. Image Handling
Use `@sanity/image-url` to generate optimized image URLs.
```typescript
importimageUrlBuilderfrom"@sanity/image-url";
import{sanityClient}from"sanity:client";
constbuilder=imageUrlBuilder(sanityClient);
exportfunctionurlFor(source){
returnbuilder.image(source);
}
```
## 5. Visual Editing (Live Preview)
Astro handles visual editing slightly differently depending on if you are using Hybrid or Static mode.
### Setup
Ensure `stega` is enabled in your client configuration if you want clickable overlays.
For real-time updates in the presentation tool, you typically need a React component wrapper (since Astro components don't re-render on the client) or use the View Transitions API with a loader.
_Note: The `@sanity/astro` integration is evolving. Check the latest docs for "Visual Editing" support._
description:Rules for Sanity Blueprints, the Infrastructure as Code solution for managing Sanity resources declaratively. Covers blueprint files, stacks and scope, the plan/deploy workflow, deletion policies, error recovery, and CI deployment.
---
# Sanity Blueprints
Sanity's Infrastructure as Code (IaC) solution. Define resources declaratively in `sanity.blueprint.ts`, track the file in version control, preview changes with `plan`, apply them with `deploy`. Blueprints is the only way to deploy Sanity Functions.
The CLI is built for unattended use: every command accepts `--json`, all IDs can be supplied via flags or environment variables, and `--help` on any command is the authoritative reference. Always run via `npx sanity@latest blueprints <command>`.
| **Blueprint file** | `sanity.blueprint.ts`, the declarative manifest of desired resources. The source of truth: if it's not in the file, it's not deployed |
| **Resource** | One managed thing (function, CORS origin, webhook, role, robot token, dataset). Identified by a unique `name` |
| **Definer** | Typed helper from `@sanity/blueprints` (e.g. `defineCorsOrigin`) that declares a resource and validates input as you write |
| **Stack** | The deployed counterpart of the file: the live resources on Sanity's side. One file can deploy to many Stacks (e.g. `staging`, `production`) |
| **Config file** | `.sanity/blueprint.config.json`, links the local file to a Stack and records scope. Gitignored by `init` automatically; not secret |
| **Scope** | `project` or `organization`. Determines which resource types a Stack can manage |
### Key behaviors
- Resources are matched **by `name`** between file and Stack. `plan` buckets every resource as create, update, no-op, or destroy.
- **There is no rename.** Changing a resource's `name` destroys the old resource and creates a new one under the new name.
- **Removal is destruction.** Deleting a resource from the file destroys it on the next deploy (subject to its deletion policy). `blueprints destroy` is equivalent to deploying an empty file.
- Deploys apply in dependency order. On partial failure, completed changes are rolled back best-effort in reverse order. Rollback is **not atomic**; some actions may be irreversible.
| `sanity blueprints deploy` | Infrastructure resources defined in `sanity.blueprint.ts` |
| `sanity schema deploy` | Studio schema to Content Lake (for MCP and editor access) |
| `sanity deploy` | The Studio application to Sanity hosting |
## The Blueprint File
`sanity.blueprint.ts` default-exports a `defineBlueprint` call. TypeScript is idiomatic (definers validate inline and provide editor help); `.js` and hand-written `.json` also work but new files should use TypeScript.
-`resources` is an array of definer outputs. Every resource requires a unique `name`.
-`values` holds reusable file-level constants (always strings), referenced as `$.values.<key>`.
- Resource-type-specific fields are documented in the typed reference: https://reference.sanity.io/_sanity/blueprints/
### Discovering available resources
The definers exported by `@sanity/blueprints` are the canonical list of what Blueprints can manage. Discover them by inspecting the package's exports (every `define*` function is a resource definer) and reading their TSDoc for fields, defaults, and examples, or browse the typed reference at https://reference.sanity.io/_sanity/blueprints/. Do not rely on a memorized list; new definers ship additively.
When the Stack is organization-scoped, project-contained resources (CORS origins, webhooks, datasets, functions) must set their `project` field explicitly.
For the semantics of an individual resource type (what a CORS origin, webhook, or role does and which values make sense), consult that feature's own Sanity documentation. This reference covers how Blueprints manages resources, not the resources themselves.
## References and the Resource Graph
Resources reference values and each other with `$` string paths:
| `$.values.<key>` | A constant from the `values` block | At file evaluation |
| `$.resources.<name>` | Another resource in the file | At deploy time; creates a dependency edge |
| `$.resources.<name>.id` | The generated ID of that resource, as a string | At deploy time |
Blueprints builds the dependency graph from these references and orders work so dependencies are created first. Unresolved references and cycles fail validation before anything is deployed. To force ordering without a data reference, use `lifecycle: {dependsOn: '$.resources.<name>'}`.
### Referencing an existing project
The recommended pattern is an environment variable surfaced through `values`:
```typescript
import"dotenv/config";
// the CLI does not read .env files itself
exportdefaultdefineBlueprint({
values:{
projectId: process.env.SANITY_PROJECT_ID??"",
},
resources:[
defineCorsOrigin({
name:"app-cors",
project:"$.values.projectId",
origin:"https://www.example.com",
}),
],
});
```
## Deletion Policies
Set `lifecycle.deletionPolicy` on a resource to control what removal means:
| `protect` | **Deployment fails** | **Deployment fails** | Resource is never updated or destroyed |
Use `protect` for production-critical stateful resources:
```typescript
defineDataset({
name:"production",
lifecycle:{deletionPolicy:"protect"},
});
```
A deletion-policy violation is a pre-deploy validation failure, not a partial deploy: fix the file (or consciously change the policy) and re-run `plan`.
## Stacks, Environments, and Scope
One blueprint file deploys to many Stacks. The `--stack <name-or-id>` flag (on `deploy`, `plan`, `info`, `logs`) selects the target; without it, the Stack recorded in `.sanity/blueprint.config.json` is used.
- **Only `init` creates Stacks.** `--stack` never creates a Stack on a miss, so CI cannot accidentally provision infrastructure. Re-run `blueprints init .` to add another Stack for the same file.
- Accounts are currently limited to **three Stacks per project scope**.
- For per-environment differences, read an environment variable inside the file (e.g. `process.env.SANITY_ENV`) and deploy with the env var and `--stack` together. `SANITY_ENV` is a convention your file reads, not a CLI feature.
| Stored in config as | `projectId` | `organizationId` |
| Resources default to | The project | Nothing; each project-contained resource must name its `project` |
| Org-scoped resource types | Not available | Available |
Organization scope is recommended for anything beyond a single-project experiment. Convert an existing Stack with `blueprints promote` (safe, idempotent, additive, but **one-way**; requires `organization-update` admin permission).
| `mint-deploy-token` | Mint a long-lived robot token for CI deploys (`--print` for shell pipelines) |
Running `npx sanity@latest blueprints --help` will show the latest available commands.
Use `npx sanity@latest blueprints <command> --help` to see options and examples for a specific command.
Function scaffolding, local testing, secrets, and function logs live under `npx sanity@latest functions <command>` (`add`, `dev`, `test`, `env add|list|remove`, `logs`). See the `functions` reference.
npx sanity@latest blueprints plan # preview the diff, catch validation errors safely
npx sanity@latest blueprints deploy # apply
npx sanity@latest blueprints info # verify deployed state
```
Always run `plan` before `deploy`. It is read-only and surfaces validation errors, reference problems, scope mismatches, and deletion-policy violations without touching anything.
### Exit codes
`deploy` and `destroy` return: **`0`** succeeded, **`2`** failed, **`75`** accepted but completion could not be confirmed.
**✅ Correct — treat exit 75 as unknown, then verify:**
```bash
npx sanity@latest blueprints deploy ||status=$?
if["${status:-0}" -eq 75];then
npx sanity@latest blueprints info --json # confirm actual Stack state
fi
```
**❌ Incorrect — treating any nonzero exit as failure and retrying.** Exit 75 means the deployment was accepted; blindly redeploying can queue duplicate operations (deploy also refuses to start while another operation is in progress).
### Scope resolution and environment variables
For `organizationId`, `projectId`, and `stackId`, the first source that resolves wins: CLI flags → environment variables → the blueprint file module → the local config file.
| Validation error (missing/invalid field, malformed file) | Before deploy | Fix the file at the reported resource path |
| Unresolved reference or dependency cycle | Before deploy | Fix the `$.resources.<name>` reference or break the cycle |
| Scope mismatch (org-scoped resource on project Stack, or no resolvable `project`) | Before deploy | Set the resource's `project` field, or `promote` the Stack |
| Deletion-policy violation (change would remove a `retain`/`protect` resource) | Before deploy | Restore the resource, or deliberately relax its policy |
| Execution failure (underlying service rejected the change) | During deploy | Read `blueprints logs`, fix the cause, redeploy |
Recovery procedure after any failure:
1. Read the error message and resource path (`blueprints logs` re-reads failure output).
2. Fix the blueprint file (or the external condition).
3. Run `blueprints plan` to confirm the corrected diff.
4. Run `blueprints deploy` again.
Retrying without changing anything only makes sense for transient execution failures; the first four failure kinds are deterministic and will fail identically until the file changes. If local and remote state seem inconsistent, run `blueprints doctor` (and `doctor --fix`) before attempting anything destructive. After a partial-deploy rollback, `blueprints info` shows what is actually live; resources owned by other Stacks are never touched by rollback.
## CI/CD
Recommended workflow: `plan` on pull requests, `deploy` on merge to main. Official GitHub Actions: https://github.com/sanity-io/blueprints-actions
The minted robot token has exactly the role needed to plan, deploy, and destroy (`blueprints-deployer` for project scope, `blueprints-deployer-robot` for organization scope) and is revocable under Robots in Sanity Manage. Store it as a CI secret, then in the pipeline:
```bash
exportSANITY_AUTH_TOKEN=<the minted token>
exportSANITY_ORGANIZATION_ID=<orgId> # or SANITY_PROJECT_ID
The function directory name must match the `name` in the blueprint config. Each function exports a `handler` from its `index.ts` (or `index.js`).
---
## Step-by-step: Creating a Function
### 1. Initialize a Blueprint
```bash
npx sanity@latest blueprints init . \
--type ts \
--stack-name production \
--project-id <your-project-id>
```
This creates `sanity.blueprint.ts` and `.sanity/blueprint.config.json` (gitignored automatically; it links your Blueprint to a Stack and is not secret).
- Max timeout: 900s. Larger functions = slower cold starts.
### Cost
Cost = invocations × (memory GB × duration seconds). Default is 1GB memory. A function averaging 1GB and 40ms duration can run ~500k invocations within 20K GB-seconds. [Monitor usage at the organization level](https://www.sanity.io/manage).
if(!URL)thrownewError("DEPLOY_HOOK_URL is not set");
awaitfetch(URL);
console.log("Deploy hook triggered");
});
```
Set the env var: `npx sanity@latest functions env add deploy-hook DEPLOY_HOOK_URL https://...`
### Set a timestamp on first publish
Uses the same pattern as the step-by-step example above. The key insight: the `!defined(firstPublished)` GROQ filter prevents re-triggering after the field is set. The `setIfMissing` patch is a redundant safety net.
The GROQ filter ensures only English documents trigger the function. The translated document gets a different `language` value, preventing recursive triggers.
Let Sanity assign the translated document's `_id` for ordinary localized content. To find or update translations later, query by language, slug, or translation metadata instead of deriving IDs from the source document. Reserve explicit `targetDocument._id` values for singleton-style targets.
Mint a long-lived deploy token with `npx sanity@latest blueprints mint-deploy-token` (creates a robot token with the role required to plan, deploy, and destroy) and store it as a CI secret. Recommended workflow: `blueprints plan` on pull requests, `blueprints deploy` on merge to main. See the `blueprints` reference for CI environment variables and exit codes.
description:Use these rules when users ask to 'Get started with Sanity' or need help setting up a new Sanity project.
---
# Sanity Getting Started Guide
## Overview
Getting started with Sanity follows three phases:
1.**Studio & Schema** — Set up Sanity Studio and define your content model
2.**Content** — Import existing content or generate placeholder content via MCP
3.**Frontend** — Integrate with your application (framework-specific)
## Communication Style
**Keep responses succinct:**
- Tell the user what you did: "Created post schema with title, body, and slug"
- Ask direct questions: "What kind of content are you building?"
- Avoid verbose explanations of what you're about to do
- Don't explain every step unless the user asks
**Examples:**
- **Good**: "Schema deployed. Ready to add some content?"
- **Bad**: "I'm going to deploy your schema to the Content Lake so that the MCP server can recognize your new document types. This will allow..."
---
## Get Started with Sanity (Interactive Guide)
**TRIGGER PHRASE:** When the user says "Get started with Sanity" or similar, follow these steps.
**Before starting:** Let the user know they can pause and resume anytime by saying "Continue Sanity setup".
**RESUME TRIGGER:** If the user says "Continue Sanity setup", check what's already configured:
- Does `sanity.config.ts` exist (typically in a `studio/` folder)? → Studio is set up
- Are there files in `schemaTypes/`? → Schema exists
- Is there a frontend framework in `package.json`? → May need integration
Resume from where they left off.
---
## Phase 1: Studio & Schema
### Step 1: Check for Existing Studio
**Look for `sanity.config.ts` or `sanity.cli.ts` across the workspace** — in the recommended side-by-side layout the Studio lives in its own folder (`studio/`, or `studio-*` when created by the Sanity onboarding flow) next to the app folder:
**If NO Studio found:**
- Ask: "Want to create a new Sanity Studio?"
- If yes, run from the repo root — **not inside a Next.js app folder**, where the CLI would switch to its embedded flow (not recommended):
```bash
npm create sanity@latest -- --template clean --typescript --output-path studio
```
- This creates a standalone Studio in `studio/`, alongside your app folder (see `project-structure.md`)
**If Studio exists:**
- Read the config to get `projectId` and `dataset`
- Proceed to Step 2
### Step 2: Check for Existing Schema
**Look in `schemaTypes/`, `schemas/`, or `src/sanity/schemaTypes/`:**
**If NO schema found:**
- Ask: "What kind of content are you building? (e.g., Blog, E-commerce, Portfolio)"
- Create appropriate schema types based on their answer
- See `schema.md` for patterns
**If schema exists:**
- Show them what you found
- Ask: "Want to add more content types or modify existing ones?"
claude mcp add Sanity -t http https://mcp.sanity.io --scope user
```
**VS Code:** Command Palette → `MCP: Open User Configuration` → add:
```json
{
"servers": {
"Sanity": {
"type": "http",
"url": "https://mcp.sanity.io"
}
}
}
```
---
## Phase 3: Frontend Integration
### Client Bundle Warning (Vite-based frameworks)
React Router, SvelteKit, Astro, and Nuxt all run on Vite. **Any module imported by a client component will be bundled to the browser.** `process.env` doesn't exist there.
For publishable values (`projectId`, `dataset`, `apiVersion`, public studio URL), use the framework's client-safe env mechanism:
- React Router / Remix: `import.meta.env.VITE_*`
- SvelteKit: `$env/static/public`
- Astro: `import.meta.env.PUBLIC_*`
- Nuxt: `useRuntimeConfig().public`
For secrets (read tokens, webhook secrets), read `process.env.*` (or the server equivalent) **only from server-only modules** — `.server.ts`, route handlers, API endpoints. Don't centralize them in a shared `env.ts` that anything else imports.
This trap is invisible at SSR — the page renders fine on first load. It surfaces on client-side route transitions, when a lazy-loaded route chunk pulls a shared client/image module into the browser.
### Step 1: Find the App and Detect Framework
The working directory is often a parent folder with the Studio and the app side by side. Identify the app folder first: a sibling of the Studio folder with its own `package.json` (commonly `web/`). If several candidates exist, ask the user which app to integrate — never assume.
`next-sanity` is the official Sanity toolkit for Next.js. It bundles `@sanity/client`, `groq` (with `defineQuery`), and `@portabletext/react`, plus dedicated subpath exports for Next.js-specific features:
Don't also install `@sanity/client`, `@portabletext/react`, or `groq` directly — import them from `next-sanity`. `@sanity/image-url` is not bundled (yet), so add it separately.
<a href={`/${(post.slug as { current?: string })?.current}`}>{post.title as string}</a>
</li>
))}
</ul>
);
}
```
`{ next: { revalidate: 30 } }` opts the fetch into Next.js' ISR cache with a 30-second revalidation window. Tune to taste; omit `options` to use defaults.
**Render an individual post (`src/app/[slug]/page.tsx`):**
```typescript
import { PortableText, defineQuery, type SanityDocument } from "next-sanity";
For advanced patterns (TypeGen, Visual Editing with `next-sanity/visual-editing`, live content with `defineLive` from `next-sanity/live`, standalone Studio architecture), see `nextjs.md`.
### Step 3: Other Frameworks
For non-Next.js frameworks, read the corresponding rule file and follow its integration guide:
- **React Router / Remix:** `remix.md`
- **SvelteKit:** `svelte.md`
- **Nuxt:** `nuxt.md`
- **Astro:** `astro.md`
Each rule file contains framework-specific patterns for data fetching, Portable Text rendering, and Visual Editing.
### Step 4: Smoke Test
Before declaring integration done, exercise both render paths:
1. `npm run dev` (in the app folder)
2. Load the home page (lists posts).
3. **Click through to a detail page** via an in-app `<Link>` / `<a>` — do not paste the URL.
4. Open the browser console. It should be clean. No `ReferenceError: process is not defined`, no hard reload to `/`.
5. For good measure, reload the detail page directly (URL bar) — that exercises SSR.
Server-side rendering passing isn't enough. Client-side route transitions pull lazy chunks that exercise different code paths, and that's where env/bundling traps surface.
---
## What's Next
Once setup is complete, let the user know:
"You're all set! Here are some things I can help with:
- **Visual Editing** — Click-to-edit in the Presentation tool (`visual-editing.md`)
- **TypeGen** — Type-safe queries with generated types (`typegen.md`)
- **Studio Structure** — Customize the Studio sidebar (`studio-structure.md`)
- **SEO** — Metadata, sitemaps, and Open Graph (`seo.md`)
**Secrets** (read tokens, webhook secrets) stay **unprefixed** and are read via `process.env` (or the framework's server-only equivalent) from server-only modules — `*.server.ts`, route handlers, API routes. Never re-export a secret from a module that a route component can import.
---
## Common Commands
```bash
npx sanity@latest mcp configure # Configure MCP for your editor
npx sanity dev # Start Studio locally
npx sanity schema deploy # Deploy schema for MCP/editor access
npx sanity deploy # Deploy Studio to Sanity hosting
npx sanity manage # Open project settings
npm run typegen # Generate TypeScript types
```
---
## Important Notes
- **Be succinct** — Guide step-by-step without over-explaining
// ✅ Also valid: Just defineQuery (TypeGen works, but no editor highlighting)
constQUERY=defineQuery(`*[_type == "post"]`);
```
## 2. Query Fragments
Use string interpolation to reuse query logic and keep queries maintainable.
```typescript
// src/sanity/fragments/image.ts
exportconstimageFragment=/* groq */`
asset->{
_id,
url,
metadata { lqip, dimensions }
},
alt
`;
// src/sanity/queries/post.ts
import{defineQuery}from"groq";
import{imageFragment}from"../fragments/image";
exportconstPOST_QUERY=defineQuery(/* groq */`
*[_type == "post"][0] {
title,
mainImage {
${imageFragment}
}
}
`);
```
## 3. Expansion Patterns (Page Builder)
When building a Page Builder query, expand all potential component types.
**Best Practice:** Use a `pageFields` fragment or similar strategy to keep the main query clean.
```typescript
constpageBuilderExpansion=/* groq */`
pageBuilder[] {
...,
_type == "hero" => {
...,
cta[] { link, label }
},
_type == "gallery" => {
images[] { ${imageFragment} }
}
}
`;
```
## 4. Maintenance Workflow
When you add a new field or component to the Schema:
1.**Update the Query:** Add the new field/expansion to the relevant GROQ query immediately.
2.**Run TypeGen:** If you have `typegen.enabled: true` in `sanity.cli.ts`, types regenerate automatically during `sanity dev`/`sanity build`. Otherwise, run `npm run typegen` manually.
3.**Verify:** Ensure the new field is available in the generated types.
**Critical:** LQIP (Low Quality Image Placeholder) is **not automatic**. You must explicitly query it via `asset->{ metadata { lqip } }`.
### Minimal Query (No LQIP)
```groq
mainImage {
asset->{ _id, url },
alt
}
```
### Full Query (With LQIP & Dimensions)
```groq
mainImage {
asset->{
_id,
url,
metadata {
lqip, // Base64 blur placeholder
dimensions { width, height } // For aspect ratio
}
},
alt,
hotspot, // Include if using hotspot cropping
crop // Include if using cropping
}
```
**Why this matters:** Without querying `metadata.lqip`, the `blurDataURL` in your component will be `undefined` and the blur effect won't work.
## 5. Performance Tips
- **Auto Format:** Sanity CDN automatically serves WebP/AVIF if the browser supports it (no need to specify `.format('webp')` manually in most cases, but `next/image` handles this too).
- **Sizing:** Always request the exact size you need using `.width()` and `.height()` in `urlFor`. Don't download a 4000px image for a thumbnail.
description:Localization patterns for Sanity using official plugins and best practices.
---
# Sanity Localization Rules
Use the contents list to jump directly to the localization pattern you need.
## Table of Contents
- Guiding principles
- Terminology
- Locale content type
- Choosing document-level vs field-level localization
- Document-level localization
- Localized singletons
- Field-level localization
- AI-powered translation
- UI enhancement
- Frontend URL best practices
## 1. Guiding Principles
### Priority: Easy Authoring Experience
The structured nature of Sanity schemas and GROQ make it easy to parse localized content for your frontend. **Never** let frontend architecture dictate your localization approach — prioritize the editor experience.
### Avoid Content Duplication
Don't create nearly identical copies with slight differences (e.g., US vs British English). Use Portable Text marks and custom blocks to swap out words or sections as needed.
Let Sanity generate document IDs for ordinary imported content. Add schema fields for legacy identifiers or slugs, then use GROQ lookups against those fields when you need to rerun an import, patch existing documents, or create references between imported records. Set `_id` directly only for singleton documents.
Run with: `sanity migration run import-wordpress-posts --no-dry-run`
Reference: [Schema and Content Migrations](https://www.sanity.io/docs/content-lake/schema-and-content-migrations)
description:Best practices for migrating content (HTML, Markdown) into Sanity Portable Text.
---
# Sanity Content Migration Rules
## Document Identity During Import (Critical)
Let Sanity generate `_id` values for imported documents unless you are intentionally creating a singleton. Do not derive deterministic UUIDs or document IDs from slugs, file paths, legacy IDs, or related document IDs.
- Store legacy identifiers in fields such as `legacyId`, `externalId`, or `slug`.
- Make imports idempotent by looking up existing documents with GROQ before creating or patching them.
- Create relationships by querying the target document and using its real `_id` in a `reference`; do not predict `_ref` values from naming conventions.
- Reserve explicit `_id` values for singleton documents such as `settings`, `homePage`, or localized singleton IDs like `homePage-en`.
## 1. HTML Import (Legacy CMS)
Use `@portabletext/block-tools` with `JSDOM` to convert HTML to Portable Text. This covers setup, custom deserializers, pre-processing, image uploads, and wrapping in `defineMigration`.
**See `migration-html-import.md` for the full guide with working examples.**
## 2. Markdown Import (Static Sites)
Use `@portabletext/markdown` for direct, schema-aware Markdown ↔ Portable Text conversion.
**Recommended: Direct Conversion with `@portabletext/markdown`**
This handles headings, lists, bold, italic, code, links, images, and tables. Use `@portabletext/sanity-bridge` to pass your Sanity schema so only valid types are produced.
**Alternative: Markdown → HTML → Portable Text**
For complex Markdown with non-standard extensions, convert to HTML first, then use `htmlToBlocks` (see above).
1.**Parse:**`marked` or `remark` to convert MD to HTML.
2.**Convert:** Use `htmlToBlocks` from `@portabletext/block-tools`.
> **Note:** `@sanity/block-content-to-markdown` and `@sanity/block-tools` are deprecated. Use `@portabletext/markdown` and `@portabletext/block-tools` instead.
## 3. Image Handling (Universal)
Don't just link to external images. Download them and upload to Sanity Asset Pipeline.
1.**Extract:** Find `<img>` tags or Markdown image syntax.
description:Integration guide for Next.js App Router, Live Content API, and a standalone Sanity Studio.
---
# Next.js & Sanity Integration Rules
Jump to the section that matches the task instead of reading this guide end-to-end.
## Table of Contents
- Architecture patterns
- Data fetching (Live Content API)
- Caching and revalidation
- Visual Editing and clean data
- Studio setup (standalone)
- Draft Mode setup
- Error handling
- Presentation queries
- Pagination pattern
## 1. Architecture Patterns
### Option A: Standalone Studio (Recommended)
**Best for:** All new Next.js projects.
The Studio is its own app, living alongside the Next.js app in the same repo:
```
your-project/
├── studio/ # Sanity Studio (standalone)
└── web/ # Next.js frontend
```
Why standalone instead of embedding the Studio in the Next.js app:
- **Faster dev and builds:** `sanity dev` and `sanity build` run on Vite and are dramatically faster (10-30x) than compiling the Studio through `next dev` / `next build`.
- **Auto-updates:** Standalone Studios receive bugfixes and new features automatically, with no dependency bump or redeploy. Embedded Studios can't auto-update (Next.js does not support ESM with import maps), so every update means bump + deploy.
- **TypeGen watch mode:** With `sanity dev`, TypeGen regenerates types as queries change. Embedded Studios can't hook into `next dev`, so you must re-run `sanity typegen generate` manually after every query edit.
- **Content model independence:** A separate Studio keeps the content model from becoming website-centric and makes collaboration easier.
**Setup:**
- Run both apps side by side in separate terminals: `next dev` (localhost:3000) and `sanity dev` (localhost:3333).
- Add your Next.js app URL to **CORS Origins**: `npx sanity cors add http://localhost:3000 --credentials` (repeat for your production URL), or via [Sanity Manage](https://www.sanity.io/manage).
- See `project-structure.md` rule for detailed structure.
### Option B: Embedded Studio (Not Recommended)
The Studio can be mounted inside the Next.js app at `/app/studio/[[...tool]]/page.tsx` via `next-sanity/studio`. Avoid this for new projects: it slows builds, ties every Studio update to an app deploy, and rules out auto-updates and TypeGen watch mode. For maintaining or migrating an existing embedded Studio, see section 5.
## 2. Data Fetching (Live Content API)
We use `defineLive` (next-sanity v11+) to enable real-time content updates and Visual Editing automatically.
### Setup (`src/sanity/lib/live.ts`)
```typescript
import{defineLive}from"next-sanity";
import{client}from"./client";
exportconst{sanityFetch,SanityLive}=defineLive({
client: client.withConfig({
apiVersion:"2026-02-01",
}),
serverToken: process.env.SANITY_API_READ_TOKEN,
browserToken: process.env.SANITY_API_READ_TOKEN,
});
```
### Rendering (`src/app/layout.tsx`)
You **must** render `<SanityLive />` in the root layout to enable real-time updates.
**Use `defineLive` by default.** It handles fetching, caching, and invalidation automatically. Only implement manual caching when you need fine-grained control.
**Never** let Stega characters leak into `<head>` tags. Always set `stega: false` for metadata fetching.
```typescript
exportasyncfunctiongenerateMetadata({params}){
const{data}=awaitsanityFetch({
query: SEO_QUERY,
params: awaitparams,
stega: false,// 👈 Critical for SEO
});
return{title: data?.title};
}
```
### C. Static Params
When generating static params, fetch only published content and disable stega.
```typescript
exportasyncfunctiongenerateStaticParams() {
const{data}=awaitsanityFetch({
query: SLUGS_QUERY,
perspective:"published",// 👈 No drafts
stega: false,
});
returndata;
}
```
## 5. Setup: Studio (Standalone)
Create the Studio as its own app from the repo root — **not inside the Next.js app folder**, where the CLI would switch to its embedded flow:
```bash
npm create sanity@latest -- --project <projectId> --dataset production --template clean --typescript --output-path studio
```
Run it with `npm run dev` inside `studio/` (defaults to http://localhost:3333). For Visual Editing, point the Presentation Tool's `previewUrl.origin` at the Next.js app (see `visual-editing.md`).
### Migrating an Existing Embedded Studio
Embedded Studios (`<NextStudio />` mounted at a route like `/app/studio/[[...tool]]/page.tsx`) keep working, but migrating to a standalone Studio is recommended:
1. Create a standalone Studio folder as above, reusing your existing `projectId` and dataset.
2. Move `sanity.config.ts`, `sanity.cli.ts`, and your schema types into it.
3. Delete the `/app/studio/[[...tool]]/` route from the Next.js app. Keep `next-sanity` — the app still needs it for fetching, Live Content, and Visual Editing.
4. Add the app's URLs to CORS origins and set the Presentation Tool's `previewUrl.origin` to the app's URL.
## 6. Setup: Draft Mode
Enable Presentation Tool and Visual Editing by setting up a draft mode route.
For faster live editing in the Presentation Tool, use `usePresentationQuery` to fetch only the specific block being edited, rather than re-rendering the entire page.
### Why Use This
- **Without:** Editing a hero title re-fetches the whole page, re-renders all blocks
- **With:** Only the hero block re-fetches and re-renders
This is especially valuable for pages with many Page Builder blocks or complex Portable Text.
`-t ui` selects the Nuxt UI starter. `-M ""` skips the interactive module-selection prompt (empty string = no extra modules). `--packageManager npm` and `--no-gitInit` suppress the other two prompts so the scaffold runs end-to-end without input.
### Installation
```bash
npx nuxi@latest module add sanity
```
`nuxi module add sanity` resolves to the official `@nuxtjs/sanity` module and registers it in `nuxt.config.ts` automatically. The module bundles `@sanity/client`, `@sanity/visual-editing`, `@portabletext/vue`, and `groq` as direct dependencies — no separate installs needed.
`groq` and `defineQuery` are also **auto-imported** by the module, so you can use them in `.vue` files without an `import` statement.
For manual image-URL building (an alternative to the auto-registered `<SanityImage>` component), add `@sanity/image-url`:
```bash
npm install @sanity/image-url
```
### What the module auto-imports
**Composables** (use directly in `<script setup>`, no imports needed):
-`<SanityContent>` — Portable Text renderer (uses `@portabletext/vue` internally; prop is `:value`)
-`<SanityImage>` — image renderer; takes an `assetId` (the image asset's `_ref`); upgrades to `<NuxtImg>` automatically when `@nuxt/image` is installed
-`<SanityFile>` — file renderer
### Configuration (`nuxt.config.ts`)
```typescript
exportdefaultdefineNuxtConfig({
modules:["@nuxtjs/sanity"],
sanity:{
projectId: process.env.NUXT_SANITY_PROJECT_ID,
dataset: process.env.NUXT_SANITY_DATASET,
apiVersion:"2026-05-15",
// Live Visual Editing Configuration
visualEditing:{
studioUrl: process.env.NUXT_SANITY_STUDIO_URL,
token: process.env.NUXT_SANITY_API_READ_TOKEN,// Required for fetching drafts
stega: true,// Enable stega for visual editing
mode:"live-visual-editing",// Default: enables live updates
},
},
});
```
**Important:** Don't enable the `minimal` client if you want the full feature set (composables, components, visual editing).
## 2. Data Fetching
### `useSanityQuery`
Use the composable for reactive fetching. It handles preview state automatically when `visualEditing` is configured. `groq` and `defineQuery` are auto-imported — use either.
Pull the slug off `useRoute()` and pass it as a query parameter. The `<SanityContent>` component renders Portable Text — note the prop is `value`, not `blocks` (renamed in v2).
When `visualEditing` is configured in `nuxt.config.ts`, the module handles:
1. Injecting the Visual Editing overlays.
2. Refreshing data when content changes in the Studio.
3. Enabling Stega encoding.
### Handling Stega in Logic
If you use stega-encoded strings in logic (e.g. `v-if="post.layout === 'full'"`), you must clean them. `stegaClean` is exported from `@sanity/client/stega` (a transitive of `@nuxtjs/sanity`, so no separate install).
**Option A — `<SanityImage>` (recommended).** Auto-registered. Takes the asset's `_ref` (the `assetId`) and builds the URL via the module's resolved projectId/dataset. If `@nuxt/image` is installed, it transparently upgrades to `<NuxtImg>` for responsive sizing.
**Option B — `@sanity/image-url` builder.** Install `@sanity/image-url` separately and build URLs manually. Useful when you need fine-grained control (hotspot/crop, format negotiation, srcset).
description:Patterns for Sanity Page Builder arrays, block components, and live editing.
---
# Sanity Page Builder Patterns
This guide covers **Page Builder** patterns—arrays of block objects that allow content teams to compose flexible page layouts. For Portable Text (rich text within documents), see `portable-text.md`.
## 1. What is a Page Builder?
A page builder is an **array of objects** (`pageBuilder[]`) that allows content teams to compose pages from reusable blocks without developer intervention.
## 5. Presentation Queries for Live Editing (Next.js)
For faster live updates in the Presentation Tool, use **presentation queries** that fetch only the specific block being edited, rather than re-fetching the entire page.
> **Note:** This pattern uses `usePresentationQuery` from `next-sanity/hooks`. For other frameworks, check your loader package for equivalent functionality.
description:Portable Text (Rich Text) rendering and custom component creation for React/Next.js.
---
# Sanity Portable Text Rules
Portable Text is Sanity's rich text format, used for content like article bodies (`body[]`). This guide covers rendering and creating custom PTE components.
**Note:** For page-level layout blocks (`pageBuilder[]`), see `page-builder.md`.
## 1. The Component
Use the `PortableText` component from `next-sanity` (or `@portabletext/react`).
```typescript
import{PortableText}from"next-sanity";
// or import { PortableText } from "@portabletext/react";
When Visual Editing is enabled, text content contains invisible stega characters for click-to-edit functionality.
**For text rendering:** Let stega characters pass through—they enable overlays:
```typescript
// Good - stega preserved for click-to-edit
<h2>{children}</h2>
```
**For logic/comparisons:** Clean the values first:
```typescript
import{stegaClean}from'@sanity/client/stega'
// Clean before using in logic
constcleanedStyle=stegaClean(block.style)
if(cleanedStyle==='h2'){...}
```
## 10. Type Safety
When using TypeGen, the Portable Text value usually has a complex generated type. You can often use `any` or `PortableTextBlock[]` for the _prop_, but cast specific blocks if needed.
```typescript
import{PortableTextBlock}from"next-sanity";
typeProps={
value: PortableTextBlock[];
};
```
## 11. Best Practices
- **Tailwind Typography:** For simple blogs, wrap `<PortableText />` in a `<div className="prose">` (from `@tailwindcss/typography`) instead of manually styling every block.
- **Handling Nulls:** Always check if `value` exists and is an array before rendering.
- **Keys:** The `PortableText` component handles React keys automatically using the `_key` from Sanity. Do not add keys manually.
- **Separate from Page Builder:** PTE blocks live in `body[]` (rich text fields), not `pageBuilder[]` (page layout). Keep these patterns separate.
description:Project structure patterns for Sanity projects including standalone Studio and monorepo setups.
---
# Sanity Project Structure
## Standalone Studio
Best for content-only projects, API-first architectures, or when frontend is managed separately.
```
your-project/
├── schemaTypes/
│ ├── index.ts
│ ├── documents/
│ ├── objects/
│ └── blocks/
├── sanity.config.ts
├── sanity.cli.ts
└── package.json
```
**Use cases:**
- Content modeling with MCP/AI tools (no frontend needed)
- Headless CMS with external consumers
- Prototyping and content design
## Monorepo (Recommended with a frontend)
Best for most projects pairing Sanity with a Next.js (or other framework) app. The Studio stays standalone — Vite-based dev/builds, auto-updates, TypeGen watch mode — while living in the same repo as the frontend.
No workspace tooling is required — each app manages its own dependencies. For larger repos, the same shape works under `apps/` with npm or pnpm workspaces.
**Setup:**
1. Add the web app URL to CORS origins: `npx sanity cors add http://localhost:3000 --credentials` (or via [Sanity Manage](https://www.sanity.io/manage))
2. Configure `typegen` in `studio/sanity.cli.ts` to read queries from `../web` and output types to `../web/sanity.types.ts` (see `typegen.md`)
3. Optionally add a root `package.json` with scripts that run both dev servers
## Embedded Studio (Legacy — Not Recommended)
Older Next.js projects may mount the Studio inside the app at `src/app/studio/[[...tool]]/page.tsx`, with `sanity.config.ts` in the app root. This still works but is no longer recommended: it slows builds, ties Studio updates to app deploys, and rules out auto-updates and TypeGen watch mode. See `nextjs.md` for the rationale and migration steps.
## File Naming Conventions
- **kebab-case** for all files: `user-profile.ts`, `hero-block.ts`
-`.ts` for schemas/utilities, `.tsx` for React components
- Each schema exports a named const matching filename
description:Integration guide for React Router v7 (and Remix v2) with Sanity, including loaders and visual editing.
---
# React Router (Remix) & Sanity Integration Rules
## Version Note
The primary examples below use **React Router v7** (the current shape — Remix v2 was renamed to React Router v7 starting with the v7 release). Import paths and the route-types file (`./+types/<route>`) come from the `react-router` package and the framework's typegen.
If you are on the older **Remix v2** stack, the integration shape is identical; only the import paths differ:
`-y` accepts defaults. The Sanity packages cover server loaders (`@sanity/react-loader`, `@sanity/client`), live preview (`@sanity/visual-editing`), Portable Text rendering (`@portabletext/react`), and typed queries (`groq`).
To support both server-side fetching and client-side live previews, use the **Split Loader Pattern**.
### A. Environment Variables
React Router runs on Vite. **Any module reachable from a route component gets bundled into the client** — `process.env` doesn't exist there and will throw `ReferenceError: process is not defined` on client-side route transitions (SSR will still work, which makes this trap easy to miss).
Split publishable values from secrets:
- **Publishable** (`projectId`, `dataset`, `apiVersion`, `studioUrl`): prefix with `VITE_` and read via `import.meta.env`. Safe to import from anywhere.
- **Secrets** (read tokens, webhook secrets): keep unprefixed and read via `process.env`**only inside `*.server.ts` files**. Never re-export them from a shared module.
### C. Server Loader (`app/sanity/loader.server.ts`)
Initializes the server client. Read the token directly from `process.env` here — do **not** import it from `env.ts`, or it will leak into the client bundle the moment any client-reachable module touches `env.ts`.
Use `loadQuery` from your **server** file in route loaders. Import the generated `Route` type from `./+types/<route>` — React Router writes one type module per route file.
For Remix v2: replace `Route.ComponentProps` / `Route.LoaderArgs` with `useLoaderData<typeof loader>()` and `LoaderFunctionArgs` from `@remix-run/node`.
This route is the canonical shape that exposes the env trap: `urlFor` → `client.ts` → `env.ts`. If `env.ts` reads `process.env`, the route works under SSR (curl returns HTML) but the client-side `<Link>` navigation will throw `ReferenceError: process is not defined` in the browser console and React Router will hard-reload back to `/`.
## 4. Real-time Preview & Visual Editing
### A. Use `useQuery` in Components
Import `useQuery` from your **shared** loader file.
**Querying:** Always include `_key` in array projections:
```groq
*[_type == "page"][0]{
pageBuilder[]{
_key, // Always include _key in queries
_type,
...
}
}
```
### B. Icons
Always assign an icon from `@sanity/icons` to documents and objects. This improves the Studio UX significantly. Browse all icons at [icons.sanity.build](https://icons.sanity.build/all).
| Content Type | Icon |
| -------------- | ------------------ |
| Article, Post | `DocumentTextIcon` |
| Author, Person | `UserIcon` |
| Category, Tag | `TagIcon` |
| Settings | `CogIcon` |
| Page | `DocumentIcon` |
| Image block | `ImageIcon` |
| Video block | `PlayIcon` |
| FAQ | `HelpCircleIcon` |
| Link | `LinkIcon` |
### C. Boolean vs. List
Avoid boolean fields for binary states that might expand later.
- **Prefer:** `options.list` with "radio" layout.
```typescript
defineField({
name:"status",
type:"string",
options:{
list:[
{title:"Draft",value:"draft"},
{title:"Published",value:"published"},
],
layout:"radio",
},
});
```
### D. The "Toggle" Pattern (Conditional Fields)
Use a radio/boolean field to toggle visibility of other fields (often grouped in fieldsets).
Sanity document `_id` values are implementation identifiers, not a content modeling tool.
- **Prefer generated IDs:** Let Sanity assign `_id` values for ordinary content documents. Avoid deterministic UUIDs, slug-derived IDs, and IDs copied from legacy systems.
- **Use relationships, not ID conventions:** Connect documents with `reference` fields and set `_ref` from an actual lookup or from the `_id` returned after creating the related document.
- **Store source identity as content:** For imports, put legacy IDs, external IDs, or stable slugs in explicit fields such as `legacyId`, `externalId`, or `slug`, then query by those fields when you need to find or upsert content.
- **Keep explicit IDs rare:** Directly setting `_id` is mainly useful for singleton documents managed through Studio Structure, such as `settings` or localized singletons like `homePage-en`.
## 7. Safe Schema Updates (The Deprecation Pattern)
**NEVER** delete a field that contains production data. It will cause data loss or Studio crashes. Instead, follow the **ReadOnly -> Hidden -> Deprecated** lifecycle.
### The Pattern
1.**`deprecated`**: Adds a visual warning and reason.
2.**`readOnly: true`**: Prevents new edits but keeps data visible.
3.**`hidden`**: Hides it from _new_ documents (where value is undefined).
4.**`initialValue: undefined`**: Ensures new documents don't get this field.
```typescript
defineField({
name:"oldTitle",// The field you want to remove
title:"Article Title (Deprecated)",
type:"string",
deprecated:{
reason:'Use the new "seoTitle" field instead. This will be removed in v2.',
},
readOnly: true,
hidden:({value})=>value===undefined,
initialValue: undefined,
});
```
### Migration Workflow
**Phase 1: Deprecate** — Apply the deprecation pattern above. Deploy.
**Phase 2: Migrate** — Update frontend to use new fields (with `coalesce()` fallbacks). Create a migration:
1.**Singletons First:** Place critical site-wide settings (Global Settings, Homepage) at the top.
2.**Dividers:** Use `S.divider()` to visually separate logical groups.
3.**Filtered Lists:** Always exclude Singleton documents from generic `documentTypeList` items to avoid duplication.
## 4. Singleton Pattern (Critical)
**Singletons are enforced via Structure, NOT schema options.** There is no `singleton: true` schema option.
This is the main case where explicit document IDs are appropriate. For ordinary content documents, let Sanity generate `_id` values and use references or GROQ lookups to connect records.
### How Singletons Work
1. Use `S.document().documentId('fixed-id')` to lock the document to a specific ID.
2. Filter the type from generic lists to prevent duplicate entries.
description:Integration guide for SvelteKit with Sanity using @sanity/sveltekit, including Visual Editing and Preview Mode.
---
# SvelteKit & Sanity Integration Rules
This guide uses the official **`@sanity/sveltekit`** package (Svelte 5 + SvelteKit 2). The older `@sanity/svelte-loader` does not work with Svelte 5 — its `useQuery` store returns empty on the client. Use `@sanity/sveltekit` instead.
`--template minimal` is the bare app. `--types ts` enables TypeScript. `--no-add-ons` skips the add-on picker. `--install <pm>` chooses the package manager (`npm`, `pnpm`, `yarn`, or `bun`).
`@sanity/sveltekit` is the one-stop integration: it bundles `@sanity/client`, `@sanity/visual-editing`, `@sanity/core-loader`, `groq`, and friends, and re-exports `createClient`, `defineQuery`, `groq`, and `stegaClean`. **Do not** also install `@sanity/client`, `@sanity/visual-editing`, or `groq` directly — import them from `@sanity/sveltekit`. `@sanity/image-url` and `@portabletext/svelte` are not bundled, so add them separately.
### Environment variables (`.env.local`)
```bash
PUBLIC_SANITY_PROJECT_ID=your-project-id
PUBLIC_SANITY_DATASET=production
PUBLIC_SANITY_API_VERSION=2026-05-15
PUBLIC_SANITY_STUDIO_URL=http://localhost:3333
SANITY_API_READ_TOKEN=
```
SvelteKit's `$env/static/public` requires the `PUBLIC_` prefix for any var read on the client. `SANITY_API_READ_TOKEN` must be declared (even empty) if any file imports it from `$env/static/private`, otherwise Vite throws at build time.
Import `createClient` from `@sanity/sveltekit`, not `@sanity/client`. `useCdn: true` is for production reads; the server (preview) client below overrides to `false`.
### `src/lib/sanity/client.server.ts` — server (preview) client
`handlePreviewMode` installs `/preview/enable` and `/preview/disable` endpoints, reads the preview cookie, and populates `locals.sanity` with `{client, fetch, loadQuery, previewEnabled, previewPerspective, browserToken}`. `handleQueryLoader` attaches `loadQuery` to `locals.sanity` for use in `+page.server.ts` / `+layout.server.ts`.
When using fetched strings for logic (routing, classNames), strip the stega markers first.
```ts
import{stegaClean}from"@sanity/sveltekit";
// …
if(stegaClean(slug)==="home"){
/* … */
}
```
## 7. Caveats
- **Yarn classic + Visual Editing.** `@sanity/visual-editing` lazy-loads a chunk that imports `react/compiler-runtime`. Yarn classic doesn't auto-install peer deps, so users who flip preview mode on with yarn classic also need `yarn add react react-dom`. (Other package managers handle this automatically.) `<VisualEditing>` only loads this chunk when `enabled === true`, so a default preview-off app is unaffected.
- **No `<slot />`.** Svelte 5 layouts use `{@render children()}`.
- **No `export let`.** Pages and components use `const {data} = $props()`.
- **`@sanity/image-url` default export.** Use the named `createImageUrlBuilder`; the default export still works but logs a runtime deprecation warning.
// Filter specific types from unions using _type discriminator
typeHeroBlock=FilterByType<PageBuilder,"hero">;
```
### Unique Query Names
All queries must have unique variable names. Duplicate names across files will cause TypeGen to silently overwrite types. Use descriptive, scoped names:
```typescript
// Unique names
constPOSTS_INDEX_QUERY=defineQuery(`*[_type == "post"]{ title }`)
Ensure `sanity.types.ts` is included in your `tsconfig.json`'s `include` array. If your config restricts includes (e.g., `["src/**/*"]`) and the types file is at the project root, TypeScript won't pick up the generated types:
```json
{
"include":["src/**/*","sanity.types.ts"]
}
```
### Skipping Individual Queries
Add `@sanity-typegen-ignore` in a comment before a query to skip type generation:
description:Comprehensive guide for Sanity Visual Editing, including Presentation Tool, Stega (Content Source Maps), and Overlays.
---
# Sanity Visual Editing Rules
## 1. Concepts
### Presentation Tool
The Studio plugin (`sanity/presentation`) that renders your front-end application inside an iframe in the Studio. It enables the "Edit" overlay and bidirectional navigation.
### Content Source Maps (Stega)
Invisible characters embedded in strings that tell the Presentation Tool which field in which document the content comes from.
- **Mechanism:** Sanity encodes document ID, field path, and dataset info into string values.
- **Result:** Click-to-edit functionality in the preview.
### Loaders
Framework-agnostic or specific libraries that handle:
1. Fetching data (production vs. preview).
2. Subscribing to real-time updates (Live Content API).
3. Encoding Stega strings (if not handled by the Content Lake automatically).
## 2. The Golden Rule of Stega (Clean Data)
When Visual Editing is enabled, string fields will contain invisible characters. You **MUST** clean them before using the value for logic.
- Use `useOptimistic` hook for instant UI updates during mutations
## 8. Optimistic Updates for Faster Editing
By default, editing a field in the Presentation Tool triggers a full page re-render. For pages with many components, this can feel sluggish. **Presentation queries** solve this by fetching only the specific block being edited.
### The Concept
Instead of:
1. User edits a field -> Full page query re-runs -> All components re-render
You get:
1. User edits a field -> Block-specific query runs -> Only that component re-renders
### How It Works
1.**Create a targeted query** that fetches just the block data using `_key`:
Hei! Det ser ut som det ikke har vært aktivitet her på en stund. Etter 30 dager vil saken bli lukket automatisk.
Om saken fortsatt er relevant eller du har oppdateringer, vil en kommentar hindre at den lukkes. Du kan alltids åpne den igjen hvis det skulle være nødvendig!
import { Meta } from "@storybook/addon-docs/blocks";
import AkselThumbnail from "./assets/aksel-thumbnail.png";
import Components from "./assets/components.png";
import Setup from "./assets/setup.png";
<Meta title="Getting started" />
<Meta title="Docs/Getting started" />
<div className="sb-container">
<div className='sb-section-title'>
# Getting started
Aksel is the toolbox for product development <a href="https://aksel.nav.no"target="_blank">@NAV</a> (Norwegian Labour and Welfare Administration)
Aksel is the toolbox for product development <a href="https://aksel.nav.no"target="_blank">@Nav</a> (Norwegian Labour and Welfare Administration)
This includes a designsystem, best practices for product-development, principles for user-experience and a blog. <a href="https://aksel.nav.no/god-praksis/artikler/om-aksel" target="_blank">
Read more about Aksel.</a>
@@ -30,7 +30,7 @@ import Setup from "./assets/setup.png";
target="_blank"
className="sb-link"
>Website</a></h3>
<p className="sb-section-item-paragraph">Includes everything you need to get started building and developing digital products for NAV today.</p>
<p className="sb-section-item-paragraph">Includes everything you need to get started building and developing digital products for Nav today.</p>
</div>
<div className="sb-section-item">
@@ -120,7 +120,7 @@ import Setup from "./assets/setup.png";
target="_blank"
className="sb-link"
>CLI</a></h3>
<p className="sb-section-item-paragraph">CLI for codemods and advanced CSS-imports</p>
<p className="sb-section-item-paragraph">CLI for codemods</p>
Loaded 100 of 5125 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.