diff --git a/src/components/Layout/LeftSidebar.test.tsx b/src/components/Layout/LeftSidebar.test.tsx
index 4bd7374109..850ecb4e89 100644
--- a/src/components/Layout/LeftSidebar.test.tsx
+++ b/src/components/Layout/LeftSidebar.test.tsx
@@ -126,4 +126,25 @@ describe('LeftSidebar', () => {
// Should not be inside an accordion trigger button
expect(sectionHeading.closest('button')).toBeNull();
});
+
+ it('renders a top-level entry with a link but no child pages as a clickable link', () => {
+ mockUseLayoutContext.mockReturnValue({
+ activePage: {
+ page: { name: 'Postgres database connector', link: '/docs/livesync/postgres' },
+ tree: [{ index: 6, page: { name: 'Ably LiveSync', link: '/docs/livesync' } }],
+ languages: [],
+ language: 'javascript',
+ product: 'liveSync',
+ template: null,
+ hasProductBar: false,
+ },
+ });
+
+ render();
+
+ // "LiveSync pricing" is a top-level link, so it should render as an anchor, not a heading
+ const pricingLink = screen.getByText('LiveSync pricing').closest('a');
+ expect(pricingLink).not.toBeNull();
+ expect(pricingLink).toHaveAttribute('href', '/docs/livesync/pricing');
+ });
});
diff --git a/src/components/Layout/LeftSidebar.tsx b/src/components/Layout/LeftSidebar.tsx
index 1ba1b4a066..fbaaffa024 100644
--- a/src/components/Layout/LeftSidebar.tsx
+++ b/src/components/Layout/LeftSidebar.tsx
@@ -58,6 +58,8 @@ const accordionTriggerClassName = cn(
const accordionLinkClassName = 'pl-3 py-1';
+const sectionHeadingClassName = 'ui-text-label2 font-bold text-neutral-1300 dark:text-neutral-000 pb-2 pt-5 pl-3 pr-2';
+
const iconClassName = 'text-neutral-1300 dark:text-neutral-000 transition-transform';
const ChildAccordion = ({ content, tree }: { content: (NavProductPage | NavProductContent)[]; tree: number[] }) => {
@@ -193,16 +195,47 @@ const ChildAccordion = ({ content, tree }: { content: (NavProductPage | NavProdu
/** Render top-level nav sections as static headings with their content always expanded. */
const SectionNav = ({ content, tree }: { content: (NavProductPage | NavProductContent)[]; tree: number[] }) => {
+ const { activePage } = useLayoutContext();
+ const location = useLocation();
+ const searchParams = useMemo(() => new URLSearchParams(location.search), [location.search]);
+
return (
{content.map((page, index) => {
const hasDeeperLayer = 'pages' in page && page.pages;
+ // A top-level entry with a link but no child pages (e.g. a product's pricing page)
+ // is a destination, not a section, so render it as a clickable link rather than a
+ // static heading.
+ if (!hasDeeperLayer && 'link' in page && page.link) {
+ const isSelected = page.link === activePage.page.link;
+
+ return (
+
+ {page.name}
+ {page.external && (
+
+ )}
+
+ );
+ }
+
return (