docs/anchor-navigation.md
This document describes the pattern for navigating directly to a specific field within a page using URL hash fragments (e.g., /account/accessibility-and-appearance#clockMode).
Rocket.Chat uses a custom SPA router that intercepts link clicks via e.preventDefault() and navigates with history.pushState(). This means the browser's native hash-based scrolling does not work for cross-page navigation. When a user clicks a link like /page-b#field, the router renders the new page but does not scroll to the target element.
Scrolling to hash targets is handled automatically by RouterProvider via useRouterScrollToHash. Any element with an id matching the URL hash will be scrolled into view on navigation.
For pages that need to adjust layout based on the hash (e.g., expanding an accordion section), useLocationHash() from @rocket.chat/ui-contexts provides the current hash value.
| File | Purpose |
|---|---|
apps/meteor/client/hooks/useRouterScrollToHash.ts | Scroll logic, called by RouterProvider |
packages/ui-contexts/src/hooks/useLocationHash.ts | Generic useLocationHash() hook from @rocket.chat/ui-contexts |
hrefid attributesuseRouterScrollToHash (in RouterProvider) scrolls to the element matching the hash via scrollIntoViewuseLocationHash() to check if a hash is presentPreferencesMessagesSection AccessibilityPage
┌──────────────────────────┐ ┌──────────────────────────┐
│ Message TimeFormat │ │ Adjustable layout │
│ [Go to accessibility] ────┼── #clockMode ───> │ <Field id="clockMode"> │
│ │ │ │
│ Hide usernames │ │ <Field id="hideUsernames"> │
│ [Go to accessibility] ────┼── #hideUsernames ───> │ │
└──────────────────────────┘ └──────────────────────────┘
id to the target elementOn the destination page, set the id on the element you want to scroll to:
<Field id='myNewField'>
<FieldLabel>{t('My_New_Field')}</FieldLabel>
</Field>
If the target field is inside a collapsed section, use useLocationHash() to expand it:
import { useLocationHash } from '@rocket.chat/ui-contexts';
const MyPage = () => {
const shouldExpand = useLocationHash().length > 1;
return (
<Accordion>
<AccordionItem defaultExpanded={shouldExpand} title={t('Section')}>
<Field id='myNewField'>
</Field>
</AccordionItem>
</Accordion>
);
};
Use the hash fragment in the href:
<FieldLink href='/account/accessibility-and-appearance#myNewField'>
{t('Go_to_accessibility_and_appearance')}
</FieldLink>
scrollIntoView is neededThe browser natively scrolls to hash targets on full page loads and un-intercepted anchor clicks. However, the custom router calls e.preventDefault() on cross-page links and uses history.pushState(), which by spec does not trigger scroll. useRouterScrollToHash compensates by calling scrollIntoView after each navigation.
Same-page hash navigation (clicking #anchor when already on that page) still works natively — the router detects matching pathnames and lets the browser handle it.
When adding a new anchor:
id attribute set on the target elementhref includes the #anchor fragmentuseLocationHash() can be called in target page to identify if theres hash and control collapsed content, if layout adjustment is needed