npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@texaryn/vue

v0.5.0

Published

Vue 3 bindings, composables, and default widgets for Texaryn

Readme

@texaryn/vue

Vue 3 bindings for Texaryn: composables, provide/inject context, a renderer and a small set of native widgets.

Status: pre-1.0. Public APIs may change before 1.0.

Install

pnpm add @texaryn/core @texaryn/schema-json @texaryn/vue vue

@texaryn/vue requires Vue 3.5 or newer, for useId.

Quick start

main.ts creates the adapter before mounting and passes it to App as a prop, because awaiting createJsonSchemaAdapter inside <script setup> would make App an async component, which renders nothing as the root without a <Suspense> boundary.

// schema.ts
export const schema = {
  $schema: 'https://json-schema.org/draft/2020-12/schema',
  type: 'object',
  properties: { name: { type: 'string', title: 'Name' } },
  required: ['name'],
}
// main.ts
import { createApp } from 'vue'
import { createJsonSchemaAdapter } from '@texaryn/schema-json'
import App from './App.vue'
import { schema } from './schema'

const adapter = await createJsonSchemaAdapter(schema)

createApp(App, { adapter }).mount('#app')
<!-- App.vue -->
<script setup lang="ts">
import type { SchemaEvaluationPort } from '@texaryn/core'
import { ErrorSummary, FormRoot, createDefaultRegistry, provideFormRuntime, useForm } from '@texaryn/vue'

const props = defineProps<{ adapter: SchemaEvaluationPort }>()

const form = useForm(props.adapter, { initialData: { name: '' } })
provideFormRuntime(form.runtime)

const registry = createDefaultRegistry()
</script>

<template>
  <!-- Both have to be descendants, not this component itself -->
  <ErrorSummary />
  <FormRoot :registry="registry" />
</template>

provideFormRuntime(runtime, { messages }) takes a whole FormMessages set from @texaryn/core, as a value, a ref or a getter, and replaces every word the built-in widgets invent. Custom widgets read it with useFormMessages(), which returns a computed.

ErrorSummary renders a named group, headed by an h2, listing every visible error with a link to the input the renderer mounted, #<prefix>-<nodeId>-input, and nothing while there are none. It takes focus once a failed submit settles, once per attempt; pass :focus="false" on all but one summary when one runtime is rendered twice. It is not a live region: the fields already announce their own errors, and the focus move is what speaks the heading.

useForm creates the runtime and ties it to the calling scope. Providing is a separate call rather than a side effect of construction: React's equivalent is <FormProvider> in the template, and Vue's is a setup call.

A component cannot inject what it provided itself, so FormRoot and anything calling useField has to be a descendant of whichever component called provideFormRuntime.

That call also opens the DOM id namespace for the form, which is why it sits there rather than in FormRoot: a sibling added later shares the same scope. Generated ids are opaque relationship identifiers rather than styling hooks, so use classes or data attributes for CSS. Uniqueness is per Vue application, so two independent apps on one page need distinct app.config.idPrefix values.

Lifetime is a requirement, not a convention

useForm and useStore throw when called outside setup() or an active effectScope. Both own something that only teardown releases, a runtime with live validation timers and a store subscription, and neither returns a handle the caller could stop. Requiring a scope is what makes that ownership real: the check runs before anything is created, so a misuse cannot leak the thing it was about to allocate. Stopping the scope destroys the runtime and unsubscribes.

What it does not depend on

@texaryn/core and Vue. Not @texaryn/react. The point of a second binding is to find out what core actually promises, and reaching into the first binding for a helper would answer a different question.

Node ids are accepted as refs or getters, and that matters

useField and useFieldArray take MaybeRefOrGetter<NodeId>, not NodeId.

Node ids follow position in the document. Array item identity is carried separately as StableItemId, and a row keyed on that keeps its component instance through a move while the node underneath it becomes a different one. React re-reads the id on every render, so it follows for free. A Vue composable that captured the id in setup would keep reading the old position and keep writing to it, so typing into a moved row would overwrite the row that took its place.

The same reasoning applies to reads. getNodeState is a map lookup with no reactivity of its own, and the runtime replaces a node's state bundle when its id leaves the document and returns. useField reads the document ref when it resolves the bundle so a recompile re-resolves it.

Both are covered by tests that fail when the behaviour is removed.

Conformance

tests/example-conformance/vue.test.ts runs the whole catalog through this package and makes the same two claims the React renderers make: every example mounts without a console error or warning, and every active node in every example resolves a widget. The framework-neutral half of that harness is shared; mounting is not, because the two frameworks do not share a mounting model.

No single file components

The package ships plain TypeScript with render functions, so it builds with the same tsc --build as every other package in the workspace and needs no Vue-specific toolchain to compile. Consumers are free to use SFCs; nothing here requires them to.

Related packages

See the repository README for the complete architecture.

License

Apache-2.0