MDK Logo

Install and wire the React packages

[⏱️ ~3 min] Install the three MDK React packages, wrap your app in MdkProvider, and wire headless stores

This page walks through the minimum integration of the MDK UI toolkit into a React application. Reference and component pages link here as their shared installation prerequisite.

Prerequisites

  • Node.js >=24
  • npm >=11
  • React 19+ and react-dom 19+

Install

npm install \
  @tetherto/mdk-react-devkit \
  @tetherto/mdk-react-adapter \
  @tetherto/mdk-ui-foundation

Wrap your app in <MdkProvider> from @tetherto/mdk-react-adapter when using connected foundation components or adapter store hooks.

Wrap your app in MdkProvider

MdkProvider sets up the TanStack QueryClient, resolves the API base URL, and publishes the auth provider to everything below it. Pass auth: the default assumes a redirect flow and cannot sign in without a redirect target, and every Gateway route is permission-checked, so a provider without it answers 401 on every call. For a demo against fixtures with no backend, noAuth() is the honest choice.

apiBaseUrl="" means same-origin — use it when the Gateway serves the UI. The provider also accepts onSessionExpired and a pre-built queryClient; only children is required.

// main.tsx
import { MdkProvider } from '@tetherto/mdk-react-adapter'
import { passwordAuth } from '@tetherto/mdk-ui-foundation'
import '@tetherto/mdk-react-devkit/styles.css'
import '@tetherto/mdk-react-devkit/styles-domain.css'

// How the session works. `passwordAuth()` signs in through the Gateway's
// `/auth/login`; the access token is short-lived and renewed on a 401.
const auth = passwordAuth()

// One silent restore from the session cookie, so a reload stays signed in.
void auth.restore().finally(() => {
  ReactDOM.createRoot(rootElement).render(
    <MdkProvider apiBaseUrl="" auth={auth} fetcher={auth.fetcher}>
      <App />
    </MdkProvider>,
  )
})

apiBaseUrl must point at a Gateway that mounts the routes your pages read. A Gateway serves only what the apps it declares provide, so the endpoints behind these hooks come from an app you ship. Writing an app's routes and declaring it in your stack covers that side.

Use the adapter hooks inside React

Each hook subscribes the component to the relevant Zustand store and re-renders only when the selected slice changes.

import { useAuth, useDevices } from '@tetherto/mdk-react-adapter'

const Toolbar = () => {
  const { permissions } = useAuth()
  const { selectedDevices } = useDevices()
  // ...
}

Or read / write stores directly outside React

The vanilla stores expose getState() / setState() so utility code, side-effect handlers, and tests can interact with the same source of truth.

import { devicesStore, timezoneStore } from '@tetherto/mdk-ui-foundation'

// Outside React (utilities, sagas, etc.) you can read/write directly:
devicesStore.getState().setSelectedDevices([])
timezoneStore.getState().setTimezone('UTC')

Theme via design tokens and @layer mdk

The compiled stylesheet declares @layer base, mdk, app, so unlayered or @layer app styles in your application always win against devkit component styles. MDK ships with --mdk-color-primary: #f7931a; override tokens in :root only when reskinning.

/* app.css — imported AFTER @tetherto/mdk-react-devkit/styles.css */
:root {
  --mdk-color-primary: #f7931a;
  --mdk-radius: 6px;
}

@layer app {
  .mdk-button--variant-primary { letter-spacing: 0.04em; }
}

Next steps

On this page