Use UI Core headlessly
Drive @tetherto/mdk-ui-core stores from any runtime without React
@tetherto/mdk-ui-core
@tetherto/mdk-ui-core is the framework-agnostic headless layer of the MDK App Toolkit. This how-to walks through installing it on its own and driving its Zustand stores from a non-React runtime — a Node script, a Vue or Svelte adapter you're authoring, a CLI tool, or a test helper.
When to reach for this
Use headless UI Core when:
- You're authoring a framework adapter (Vue, Svelte, Web Components) and need raw access to the Zustand stores.
- You're building a Node CLI or backend service that has to read MDK telemetry and act on it.
- You're writing test helpers or fixtures that need to seed and inspect store state without a React renderer.
- You need to subscribe to store changes from non-UI code — logging, websocket bridges, metrics.
For a React app, the React adapter wraps UI Core with <MdkProvider> and adapter hooks. Use that path instead so most React code never touches @tetherto/mdk-ui-core directly.
Install
@tetherto/mdk-ui-core has no peer dependencies on React or any UI framework.
npm install @tetherto/mdk-ui-coreSubpath imports
Pull only the pieces you need from the relevant subpath. Subpath imports give tree-shakers a smaller surface than the top-level barrel:
import { authStore, devicesStore } from '@tetherto/mdk-ui-core/store'
import { createMdkQueryClient } from '@tetherto/mdk-ui-core/query'
import type { Device } from '@tetherto/mdk-ui-core/types'The subpath exports table on the reference lists every supported entry.
Create a QueryClient
createMdkQueryClient returns a TanStack Query Core client wired to your App Node. Pass an explicit baseUrl, or let the factory resolve one from environment variables:
import { createMdkQueryClient } from '@tetherto/mdk-ui-core/query'
const queryClient = createMdkQueryClient({
baseUrl: 'https://app-node.example.com',
})Without an explicit baseUrl, the factory checks VITE_MDK_API_URL then MDK_API_URL before falling back to http://localhost:3000. The resolution order on the reference covers every case.
Read store state
Each store is a Zustand vanilla singleton. getState() returns the current snapshot:
import { authStore } from '@tetherto/mdk-ui-core/store'
const { token, permissions } = authStore.getState()
console.log('current token', token)Write store state
setState() accepts either a partial object or a function that receives the previous state:
import { devicesStore } from '@tetherto/mdk-ui-core/store'
devicesStore.setState({ selectedDeviceId: 'wm-002' })
devicesStore.setState((prev) => ({
devices: [...prev.devices, newDevice],
}))Subscribe to changes
subscribe() runs a callback on every state change and returns an unsubscribe function:
import { notificationStore } from '@tetherto/mdk-ui-core/store'
const unsubscribe = notificationStore.subscribe((state) => {
console.log('unread notifications:', state.count)
})
unsubscribe()A complete Node example
A small Node script that authenticates against the App Node, fetches the device list once, and then tails unread notification count changes:
import { createMdkQueryClient } from '@tetherto/mdk-ui-core/query'
import {
authStore,
devicesStore,
notificationStore,
} from '@tetherto/mdk-ui-core/store'
async function main() {
const queryClient = createMdkQueryClient({
baseUrl: process.env.MDK_API_URL ?? 'http://localhost:3000',
})
authStore.setState({ token: process.env.MDK_TOKEN ?? '' })
const devices = await queryClient.fetchQuery({
queryKey: ['devices', 'list'],
queryFn: async () => {
const res = await fetch(`${process.env.MDK_API_URL}/api/devices`, {
headers: { Authorization: `Bearer ${authStore.getState().token}` },
})
return res.json()
},
})
devicesStore.setState({ devices })
console.log(`Found ${devices.length} devices`)
const unsubscribe = notificationStore.subscribe((state) => {
console.log(`unread notifications: ${state.count}`)
})
process.on('SIGINT', () => {
unsubscribe()
process.exit(0)
})
}
main().catch((err) => {
console.error(err)
process.exit(1)
})Run it with:
MDK_TOKEN=ey... MDK_API_URL=https://app-node.example.com node script.tsFor the prebuilt query and mutation factories (authQuery, devicesQuery, deviceQuery, telemetryQuery), check the QueryClient factory section on the reference.
Next steps
- UI Core reference: full store list, query helpers, and the
createMdkQueryClientresolution order. - MDK App Toolkit architecture: where UI Core fits in the frontend stack.
- React adapter: if you decide to layer React on top.