x-pack/platform/packages/shared/kbn-ink/src/router/README.md
Ink-compatible routing utilities built on react-router-dom-v5-compat, designed for CLI UIs using Ink.
This package provides:
InkRouter: A memory router wrapper for Ink apps.InkRoutes and InkRoute: The @kbn/ink/router variants of Routes and Route.RouteMenu: A helper that renders a navigable menu and generates sub-routes from items.useInkRouter(): Hook for go() (navigate) and back() (exit at top level or navigate up).useActiveRoutes(): Hook to observe currently rendered routes (useful for breadcrumbs or context-aware UI).Wrap your app with InkRouter, then declare routes using InkRoutes and InkRoute:
import React from 'react';
import { render } from 'ink';
import { InkRouter } from './ink_router';
import { InkRoutes } from './ink_routes';
import { InkRoute } from './ink_route';
import { Outlet } from 'react-router-dom-v5-compat';
import { Text } from 'ink';
const App = () => (
<InkRouter initialPath="/">
<InkRoutes>
<InkRoute element={<Outlet />}>
<InkRoute path="hello/*" element={<Text>Hello!</Text>} />
</InkRoute>
</InkRoutes>
</InkRouter>
);
render(<App />);
RouteMenu renders a menu. Two kinds of menu items are supported:
import React from 'react';
import { InkRouter } from './ink_router';
import { RouteMenu } from './route_menu';
import { InkRoutes } from './ink_routes';
import { InkRoute } from './ink_route';
import { Text } from 'ink';
export const App = () => (
<InkRouter initialPath="/">
<RouteMenu
label="Main"
items={[
{ label: 'Child', path: 'child', element: <Text>child</Text> },
{ label: 'Absolute', path: '/absolute', element: <Text>absolute</Text> },
{
label: 'Do Something',
onSelect() {
/* do something */
},
},
]}
/>
</InkRouter>
);
Navigate programmatically and handle back/exit behavior.
import React, { useEffect } from 'react';
import { useInkRouter } from './use_ink_router';
import { useLocation } from 'react-router-dom-v5-compat';
import { Text } from 'ink';
export function Navigator({ to }: { to?: string }) {
const { go, back } = useInkRouter();
const location = useLocation();
useEffect(() => {
if (to) {
go(to); // relative by default; use '/abs' for absolute
} else {
back(); // exits app at top level, or navigates up one nested level
}
}, [to, go, back]);
return <Text>Current: {location.pathname}</Text>;
}
Behavior:
go(path, options = { relative: 'path' }) normalizes slashes and navigates.back() uses the current active route stack to navigate up; if already at top level it calls Ink's exit().Observe currently rendered routes, including optional handle and URL params. Helpful for breadcrumbs or contextual UI.
import React from 'react';
import { useActiveRoutes } from './use_active_routes';
import { Text } from 'ink';
export function Breadcrumbs() {
const routes = useActiveRoutes();
return (
<Text>
{routes
.map((r) => r.path)
.filter(Boolean)
.join(' / ')}
</Text>
);
}
'child') for route menu items inherit the parent's path.