Table of contents
An "On this page" list of links that marks the section the reader has scrolled to.
Overview
Northwind Sync keeps customer records in your CRM and your billing system in step. Every change on either side becomes an event, and the sync service applies it to the other side within a few seconds.
This guide walks through connecting both systems, choosing which fields to map, and rolling the integration out to your whole team without interrupting billing.
Connect accounts
Open Settings → Integrations and choose Northwind Sync. You will be asked to sign in to both systems with an administrator account; the service stores only scoped tokens, never passwords.
Once both accounts are connected, a test record is created and deleted on each side to confirm permissions. If the check fails, the most common cause is a CRM role without API access.
Field mapping
Standard fields such as company name, billing email and tax ID are mapped automatically. Custom fields appear in a second list where you choose a target and a direction.
Use one-way mappings for values that have a single owner, such as plan tier from billing. Two-way mappings resolve conflicts by keeping the most recent edit.
Rollout
Start with a single sales region. The sync dashboard shows applied, skipped and failed events per hour, so you can confirm the mapping before widening it.
When the error rate stays under one percent for a week, enable the remaining regions. Historical records are backfilled overnight in batches of five thousand.
Troubleshooting
Failed events are retried with exponential backoff for up to 24 hours. After that they move to the review queue, where you can edit the payload and replay it.
If records drift apart, run a reconciliation from the dashboard. It compares both systems field by field and lists every difference before changing anything.
Overview
Northwind Sync keeps customer records in your CRM and your billing system in step. Every change on either side becomes an event, and the sync service applies it to the other side within a few seconds.
This guide walks through connecting both systems, choosing which fields to map, and rolling the integration out to your whole team without interrupting billing.
Connect accounts
Open Settings → Integrations and choose Northwind Sync. You will be asked to sign in to both systems with an administrator account; the service stores only scoped tokens, never passwords.
Once both accounts are connected, a test record is created and deleted on each side to confirm permissions. If the check fails, the most common cause is a CRM role without API access.
Field mapping
Standard fields such as company name, billing email and tax ID are mapped automatically. Custom fields appear in a second list where you choose a target and a direction.
Use one-way mappings for values that have a single owner, such as plan tier from billing. Two-way mappings resolve conflicts by keeping the most recent edit.
Rollout
Start with a single sales region. The sync dashboard shows applied, skipped and failed events per hour, so you can confirm the mapping before widening it.
When the error rate stays under one percent for a week, enable the remaining regions. Historical records are backfilled overnight in batches of five thousand.
Troubleshooting
Failed events are retried with exponential backoff for up to 24 hours. After that they move to the review queue, where you can edit the payload and replay it.
If records drift apart, run a reconciliation from the dashboard. It compares both systems field by field and lists every difference before changing anything.
Usage
Each item names its target with href="#id"; the component never scans the page for headings. It finds the scroll container that holds every target, the document or a Scroll area as here, and marks the innermost target whose region crosses the reading line. Place it in a sticky column inside that same container.
When the list is longer than its column, the current link is kept in view inside the column only.
An item names one element by href="#id", resolved in the item’s own tree scope and then the document, or by the target property, which takes an element reference (useful inside shadow roots or for elements without ids). Headings, sections, cards and figures all work the same way. Hidden targets are skipped until they show again; unresolved targets raise a tp-diagnostic.
Set scroll-root (an id) or scrollRoot (an element; a component with a viewportElement, such as Scroll area, resolves to its viewport) to choose it explicitly.
Navigation
Activating an item scrolls only the content’s container and keeps that item current until the reader scrolls again. The default navigation="fragment" also records the target in the URL as a history entry, so Back returns to the previous section. Use navigation="scroll", as these examples do, when your application routes on the fragment or the list sits in an embedded panel.
scroll-behavior chooses smooth (default), instant, or auto (the container’s CSS scroll-behavior); reduced motion is always instant. Focus does not move.
Links in the content work like the items: a heading’s own # anchor or a cross-reference such as <a href="#theming"> scrolls the same way and makes its target current. Back and forward through history and a fragment present at load do the same.
Depth and title
depth only indents an item; it is never used for tracking. label replaces the default “On this page” title, and a title slot accepts richer content. This list follows the article above: scroll it and both lists update.
Nested sections
To keep a group current while the reader is inside one of its subsections, make the group a section that wraps them and point its item at the section. The indicator then spans the group and the subsection together. This list sits in the leading column and also sets scroll-behavior="instant", so items jump without animation, and scroll-debounce="120", so the current item settles only once scrolling pauses.
People
Everyone in the workspace has one account, shared across every project they can reach. Owners manage membership from Settings → People.
Invitations
Invite by email or share a join link limited to your company domain. Pending invitations expire after fourteen days and can be resent at any time.
Roles
Members edit the projects they belong to, admins also manage people and integrations, and owners control billing. A workspace always keeps at least one owner.
Billing
Plans are billed per active member each month. Members who have not signed in for thirty days stop counting until they return.
Invoices
Invoices are emailed to the billing contact on the first of the month and stay available as PDFs under Billing → History.
Tax details
Add a VAT or GST number before the next invoice is issued. Changes are not applied to invoices that were already sent.
Offboarding
Removing a member transfers their private projects to the owner who removed them. Their comments and history stay in place.
People
Everyone in the workspace has one account, shared across every project they can reach. Owners manage membership from Settings → People.
Invitations
Invite by email or share a join link limited to your company domain. Pending invitations expire after fourteen days and can be resent at any time.
Roles
Members edit the projects they belong to, admins also manage people and integrations, and owners control billing. A workspace always keeps at least one owner.
Billing
Plans are billed per active member each month. Members who have not signed in for thirty days stop counting until they return.
Invoices
Invoices are emailed to the billing contact on the first of the month and stay available as PDFs under Billing → History.
Tax details
Add a VAT or GST number before the next invoice is issued. Changes are not applied to invoices that were already sent.
Offboarding
Removing a member transfers their private projects to the owner who removed them. Their comments and history stay in place.
Depth still only indents. The grouping comes from the sections themselves, so a flat list over the same page tracks the same way.
Targets are ordered by position. Each one covers from its start to the next target’s start, or to its own end when that is further. A heading therefore covers the text below it, a section covers itself, and a section that contains other targets contains their regions, so the containing item stays active and the indicator spans the chain.
Building items from your content
collectTargets(root, selector, options) from @tweakpad/ui returns a record for each element your selector matches; render one item per record and give it the element as target. Targets need no ids and need not be headings. Run it again when the content changes, as the button does here.
Start from a blank board or a template. Templates bring their own columns, labels and a few example tasks you can delete.
Upload a CSV from your previous tool. Columns are matched by name, and anything unmatched becomes a custom field you can rename later.
Move a task to Review when its pull request opens, or assign a reviewer when a due date is two days away.
Start from a blank board or a template. Templates bring their own columns, labels and a few example tasks you can delete.
Upload a CSV from your previous tool. Columns are matched by name, and anything unmatched becomes a custom field you can rename later.
Move a task to Review when its pull request opens, or assign a reviewer when a due date is two days away.
It has no default selector, assigns no ids and returns a depth only when you supply a depth function.
The heading selector and depth rule are the page’s choice here, not the library’s.
Compact
variant="compact" draws less attention: one short line per top-level item, with the current one marked. Hovering the lines opens the full list in a card after open-delay, and close-delay keeps it open briefly after the pointer leaves; the lines are also one Button for keyboard users. side is the direction the card grows: inline-start for lines on the end edge of the page, inline-end for lines on the start edge.
Highlights
Version 4.2 adds shared workspaces, a faster search index and a new export pipeline. Existing projects upgrade automatically the next time they open.
Shared workspaces
Invite collaborators to a workspace instead of to each project. Permissions follow the workspace, and owners can still restrict a single project to a smaller group.
Search
Results now arrive as you type, ranked by recent activity. Filters for owner, type and date can be combined and saved.
Export
Exports run in the background and notify you when they finish. PDF exports keep links and the document outline.
Highlights
Version 4.2 adds shared workspaces, a faster search index and a new export pipeline. Existing projects upgrade automatically the next time they open.
Shared workspaces
Invite collaborators to a workspace instead of to each project. Permissions follow the workspace, and owners can still restrict a single project to a smaller group.
Search
Results now arrive as you type, ranked by recent activity. Filters for owner, type and date can be combined and saved.
Export
Exports run in the background and notify you when they finish. PDF exports keep links and the document outline.
A deeper item counts toward the line before it, so a section’s line stays highlighted while you read its subsections. The current section’s line takes the indicator color, another active section’s line the muted foreground, and the rest the border color. Only the color changes, never the size of the strip.
Pressing the lines, or Enter or Space while they have focus, toggles the card and moves focus to the current link. Escape closes the card and returns focus to the lines. Activating a link scrolls as in the list and closes the card.
The card covers the lines, aligned with their outer edge and centered on them. When the card does not fit on that side it grows the other way, and it shifts to stay in the viewport. A card taller than the viewport scrolls and keeps the current link in view.
Reading line
The reading line defaults to the scroll container’s scroll-padding-block-start, the same CSS you already use to clear a sticky header for anchor links, so a jump always lands on the item it marks. activation-offset overrides it with pixels or a percentage. For busy pages, scroll-throttle and scroll-debounce reduce how often the current item updates.
Fragment navigation lands targets exactly there, after their own scroll-margin-block-start, so a jump always makes its target current.
There is always a current item. At the top, and whenever the reading line is still above the first target (an introduction, padding, or overscroll), the first target is current. Over the last screen of scrolling the reading line moves down to the end edge, so short final sections still become current in order and the last is current at the bottom, including overscroll.
Performance
Many components can react to the same scrolling without adding listeners. Scroll observation is a shared service: each scroll container has one native listener per event type, the window one ResizeObserver, and each observed root one MutationObserver, however many components subscribe (observeScroll, observeResize, observeSubtree from @tweakpad/ui).
Targets are measured only when layout changes: their size, the container’s size or scroll height, or the DOM under the container. A scroll alone recomputes from the scroll position, with no layout reads of the targets.
Each subscriber chooses its timing. The table of contents updates once per animation frame by default; scroll-throttle limits updates to one per interval (with a trailing update), and scroll-debounce updates only after scrolling pauses.
Accessibility
The root is one nav landmark named by its title, and the items form a list. Only the current link carries aria-current="location". The rail and indicator are hidden from assistive technology. Scrolling never moves focus or announces anything.
In the compact variant the landmark is named by the title text and holds a button with the same name that reports whether the card is expanded. The lines are hidden from assistive technology. The card is a dialog named by the title and holds the same list; its links are in the focus order while it is open.
API Reference
Every element also accepts motion-policy.
Attributes
valuevaluestring | nulldefault-valuedefaultValuestring | nullnullscroll-rootscrollRootIdstring | nullnullactivation-offsetactivationOffsetstring | number | nullnullscroll-padding-block-start.navigationnavigation'fragment' | 'scroll''fragment'fragment uses native fragment navigation; scroll leaves the URL untouched.scroll-behaviorscrollBehaviorScrollBehavior'smooth'auto follows the scroll root's CSS.scroll-throttlescrollThrottlenumber0scroll-debouncescrollDebouncenumber0scrollThrottle.labellabelstring''title slot replaces it, and without either the title message shows.variantvariant'list' | 'compact''list'list shows the title and list in place; compact shows marks that open them in a card.sideside'inline-start' | 'inline-end''inline-start'open-delayopenDelaynumber300close-delaycloseDelaynumber0Properties
scrollRootElement | nullnullmessagesTableOfContentsMessages{}onValueChange((event: TpValueChangeEvent<string | null>) => void) | undefinedvalue proposal.activeValuesread-onlyreadonly string[]resolvedScrollRootread-onlyHTMLElement | nulltocMessagesread-onlyRequired<TableOfContentsMessages>messages over the built-in defaults.Events
tp-value-changeTpValueChangeEvent<string | null>value proposal (scroll, link-press).Methods
navigate(value)voidrefresh()voidSlots
defaulttp-table-of-contents-item elements.titlelabel and the title message.CSS parts
::part(table-of-contents)nav landmark.::part(title)::part(list)::part(rail)::part(indicator)::part(trigger)::part(marks)::part(mark)data-active, data-current.::part(popover)<tp-table-of-contents-item>
Attributes
hrefhrefstring | nullnull#install, resolved in this item's tree scope, then the document.depthdepthnumber1Properties
targetElement | nullnullhref.activebooleanfalsecurrentbooleanfalseresolvedTargetread-onlyElement | nullfragmentread-onlystring | nullhref, or null when it names no fragment.keyread-onlystringlinkElementread-onlyHTMLAnchorElement | nullEvents
tp-table-of-contents-item-changeEventdepth changes, so the table of contents redraws its compact marks.Slots
defaultCSS parts
::part(link)data-active, data-current.