@openreachtech/furo-vue
v1.3.2
Published
furo for Vue
Readme
@openreachtech/furo-vue
furo-vue is a library that supports Vue development.
Installation
Requires Node.js ^20.19.2 and npm ^10.9.0 (the versions the CI builds against).
npm install @openreachtech/furo-vueGetting Started
This guide takes a brand-new project (one that has never used furo-vue) from
zero to a rendered component. It targets Nuxt 3 (the primary consumer
stack); plain Vue 3 + Vite differs only in how you register components (use
app.component(...) or per-component imports instead of a Nuxt plugin).
For AI agents initializing a project: follow the numbered steps below in order. To pick and call components, read the machine-readable manifest at
node_modules/@openreachtech/furo-vue/public/furo-vue/components.jsonand use thefuro-vue-componentsskill (skills/furo-vue-components/SKILL.md).
(1) Import the design tokens (required)
Every component reads CSS custom properties from a single token entry. Import it once in your app. Without it, components render unstyled.
Nuxt — add it to nuxt.config css:
// nuxt.config.js
export default defineNuxtConfig({
css: [
'@openreachtech/furo-vue/lib/assets/css/furo.css',
],
})Plain Vue 3 — import it in your entry file:
// main.js
import '@openreachtech/furo-vue/lib/assets/css/furo.css'(2) Enable light / dark theme
Tokens are defined for both themes and switch off a data-theme attribute on
<html>. Set it to light (default) or dark — component CSS responds
automatically, no media query needed.
<html data-theme="light">To toggle at runtime, set the attribute on document.documentElement:
document.documentElement.setAttribute('data-theme', 'dark')(3) Render your first component
Components are named exports. Import only what you use.
<script>
import {
FuroButton,
} from '@openreachtech/furo-vue'
export default {
components: {
FuroButton,
},
}
</script>
<template>
<FuroButton :parcel="{ variant: 'default' }">
Create record
</FuroButton>
</template>To register components globally instead of per-file (optional), create a Nuxt plugin:
// plugins/furo-vue.js
import * as furoVue from '@openreachtech/furo-vue'
export default defineNuxtPlugin(nuxtApp => {
Object.entries(furoVue)
.filter(([name]) => name.startsWith('Furo'))
.forEach(([name, component]) => {
nuxtApp.vueApp.component(name, component)
})
})(4) The component contract
Every form-control component shares one public surface — learn it once, it applies everywhere:
- Props:
parcel(a reactive behavior object) plusv-model:value. - Events: most controls emit
change-value(on input),commit-value(on blur / enter), andupdate:value(v-model sync). Not all do — read each component'seventsarray in the manifest. Action components (e.g.FuroButton) emit their own typed payloads. - No primitive imports: the public contract is Furo-defined; never import the underlying headless library.
- Logic in the Context, not the template.
<template>
<FuroTextField
v-model:value="form.name"
:parcel="{ placeholder: 'Full name' }"
@commit-value="context.onCommitName({ payload: $event })"
/>
</template>(5) Labeled form fields
Wrap any field atom in FuroControlBlock to add a label, hint, and error
message:
<template>
<FuroControlBlock :parcel="{ label: 'Email', error: context.emailError }">
<FuroEmailField
v-model:value="form.email"
:parcel="{ placeholder: '[email protected]' }"
/>
</FuroControlBlock>
</template>(6) Toasts
Mount <FuroToaster /> once near your app root, then call the imperative
toast helper from anywhere (client-side only):
<template>
<FuroToaster />
<!-- the rest of your app -->
</template>import {
toast,
} from '@openreachtech/furo-vue'
toast.show({ title: 'Saved', type: 'success' })(7) Icons the library does not ship
Icons resolve offline, so FuroIcon draws only the data it holds: the 49 icons
the library ships, plus whatever your application registers. Register yours once
at start-up, before the first render:
import {
furoIconRegistry,
} from '@openreachtech/furo-vue/icons'
furoIconRegistry.registerIconSet({
iconSet: applicationIconSet,
})A name nothing resolves throws outside a production build, naming the icon, so a missing icon never hides as an empty box. A production build draws the warning glyph instead.
To reach every name rather than registering them one by one, install your own renderer. The registry still answers first, so the library's own icons keep drawing offline whatever you install:
furoIconRegistry.useIconRenderer({
component: applicationIconComponent,
})A Nuxt application gets this from the module instead:
// nuxt.config.js
modules: ['@nuxt/icon', '@openreachtech/furo-vue/nuxt']A plain Vue application gets it from createIconifyRenderer(), exported at
@openreachtech/furo-vue/icon-renderers. Full API, including single-icon
registration and a lazy resolver:
docs/furo-icon.md.
(8) Discover every component
- Human catalog (grouped by layer, with use cases): docs/COMPONENTS.md
- Machine-readable manifest (for AI agents and tooling):
public/furo-vue/components.json - llms.txt:
public/furo-vue/llms.txt
Maintainers regenerate these from the doc registry with:
npm run docs:manifest(9) GraphQL, fetchers, and page templates (out of scope here)
furo-vue ships presentational atoms, molecules, and organisms only. GraphQL
launchers, fetcher/submitter adapters, and page-level templates (ListView,
DetailView, …) live in the consumer app. See
docs/furo-vue/README.md
for the scope boundary and
docs/furo-vue/architecture.md
for layer rules.
(9) Importing logic in a Node / test environment
The main entry (@openreachtech/furo-vue) re-exports Vue SFCs, which a plain
Node / CommonJS test runner (e.g. Jest without a Vue transform) cannot parse.
When you only need the pure-JS surface — BaseFuroContext,
BaseFuroContextAccessor, the imperative toast helper, toastQueue,
ToastQueue — import the logic-only entry, which pulls no .vue files and loads
in a jsdom-free Node environment:
import {
BaseFuroContext,
} from '@openreachtech/furo-vue/lib/index.core.js'Component (.vue) tests still go through the full entry under a jsdom + Vue
transform.
Using with Claude (AI agents)
furo-vue ships machine-readable docs so an AI agent (Claude Code, Cursor, etc.)
can scaffold components correctly without guessing the API. After install they
live inside the package:
node_modules/@openreachtech/furo-vue/public/furo-vue/llms.txt— a compact, link-based index of every component grouped by layer.node_modules/@openreachtech/furo-vue/public/furo-vue/components.json— the full manifest: for each component itsimportpath,props,parcelkeys,events,slots, andfeatures.
Point Claude Code at the manifest
Add a pointer in your project's CLAUDE.md (or AGENTS.md) so the agent reads
the contract before writing furo-vue code:
## furo-vue component library
When building UI with `@openreachtech/furo-vue`, follow its machine-readable contract:
- Index: `node_modules/@openreachtech/furo-vue/public/furo-vue/llms.txt`
- Full API manifest: `node_modules/@openreachtech/furo-vue/public/furo-vue/components.json`
Contract for every component: pass a reactive `parcel` object plus `v-model:value`,
listen to Furo emits (`change-value`, `commit-value`, `update:value`), and use the
documented slots. Do not reach into the underlying headless primitive.Ask Claude to load it on demand
In a Claude Code session you can also just say:
Read
node_modules/@openreachtech/furo-vue/public/furo-vue/components.jsonand build a login form using FuroTextField, FuroPasswordField, and FuroButton.
The manifest is regenerated from the doc registry by maintainers with
npm run docs:manifest, so it always matches the shipped components.
Add the furo-vue skill (Claude Code)
The repo ships a Claude Code skill, furo-vue-components, that routes a request
("which component for X") to the right component and its contract via the
manifest. The skill is not in the npm package, so copy it from the repo into
your project's .claude/skills/:
# from a checkout of furo-vue
cp -R skills/furo-vue-components <your-project>/.claude/skills/
# or fetch just the skill file from GitHub
mkdir -p .claude/skills/furo-vue-components
curl -fsSL https://raw.githubusercontent.com/openreachtech/furo-vue/main/skills/furo-vue-components/SKILL.md \
-o .claude/skills/furo-vue-components/SKILL.mdClaude Code auto-discovers skills under .claude/skills/. Once copied, ask
Claude to build UI and it invokes the skill, loads the manifest, and calls
components with the correct parcel + v-model:value + emits contract.
Contribution
Bug reports, feature requests, and code contributions are welcome.
Feel free to contact us through GitHub Issues.
git clone https://github.com/openreachtech/furo-vue.git
cd furo-vue
npm install
npm run lint
npm testLicense
This project is released under the Apache License 2.0.
For more details, please see in the LICENSE file.
Developer
Copyright
© 2026 Open Reach Tech Inc.
