Tooling

Configuration

unframework.config.ts tells the CLI which frameworks to build, where your components live and where the outputs go. It loads under Node's type stripping, and every mistake in it is a diagnostic that names the field.

A configuration file lets a design system say once which frameworks it ships to, where its components live and where each framework's output goes, so that every unframework build and unframework check agrees. Without one, it's easy to build four targets on your machine and seven in CI, to point outDir at a folder that holds your sources, or to misspell svelte and wonder why no Svelte output appears.

The file is unframework.config.ts, at the root of your project. The CLI reads it. The bundler plugin doesn't: in an app, the plugin takes its options in your bundler's configuration instead.

Write a config

unframework init writes this one, which names all seven targets and the default directories:

unframework.config.ts
import { defineConfig } from "unframework/config";

export default defineConfig({
  targets: ["react", "vue", "svelte", "solid", "angular", "qwik", "astro"],
  srcDir: "src",
  outDir: "dist",
});

defineConfig returns what you pass it. It's there for its types, so your editor flags a wrong field or an unknown target as you type. unframework/config imports nothing but types, so loading the config loads no compiler.

Every field is optional. A project with no config file at all takes the defaults below.

Fields

FieldDefaultWhat it sets
targetsall sevenThe frameworks to compile to: react, vue, svelte, solid, angular, qwik and astro
srcDirsrcWhere the .uf.tsx and .uf.ts sources are, relative to the config's directory
outDirdistWhere each target's tree is written, as <outDir>/<target>, relative to the config's directory
targetOptions{}Options for each target, checked against that target's schema
typeOraclesyntacticHow props types are read across files: syntactic, or tsgo for TypeScript's own checker
strictfalseWith true, a portability warning (UF4xxx) counts as an error in the CLI
package.targetstargetsWhich targets build --package packages. Each must also be in targets
package.resolveFromthe rootWhere each target's packager is installed: one directory, or one per target, like { angular: "…" }

targets needs at least one name, with none repeated. srcDir and outDir can't be empty.

strict turns only portability warnings into errors. Info diagnostics stay as they are, so a stylesheet's UF4002 on Angular and Qwik never fails a strict build.

typeOracle: "tsgo" loads @unframework/type-oracle-tsgo only when you choose it. unframework build --type-oracle and unframework check --type-oracle override the field for one run.

package.resolveFrom is for packagers that you keep somewhere other than the project's root. Our sample design system keeps the TypeScript 6 packagers of Angular, Vue and Svelte in the repository's toolchains, so its config points those three targets there:

unframework.config.ts
import { defineConfig } from "unframework/config";

export default defineConfig({
  targets: ["react", "vue", "svelte", "solid", "angular", "qwik", "astro"],
  package: {
    // Their packagers need TypeScript 6, which the workspace keeps in the toolchains (AGENTS.md).
    resolveFrom: {
      angular: "../../toolchains/angular",
      vue: "../../toolchains/vue",
      svelte: "../../toolchains/svelte",
    },
  },
});

Target options

targetOptions holds an entry per target, and each target declares the options it takes with a Standard Schema. What the schema returns replaces what you wrote, so a schema can fill in defaults.

No built-in target declares options yet. A target that declares none takes none, so any key under it is an error rather than a setting nobody reads: targetOptions: { react: { compiler: true } } reports UF7004 until React declares that option.

How the file loads

The CLI looks in the directory it runs in, and loads the first of these it finds: unframework.config.ts, unframework.config.mts, unframework.config.js and unframework.config.mjs. It doesn't search the directories above.

Node loads a TypeScript config by stripping its types, so you don't need a loader. That also means the file can't use syntax that needs compiling, like an enum or a parameter property.

The config is an ES module. In a project whose package.json sets "type": "commonjs", which npm init -y writes, Node loads unframework.config.ts as CommonJS and it fails to load. Set "type": "module", or name the file unframework.config.mts. A package.json with no type works, with a warning from Node.

When it's wrong

A config that can't load, or doesn't fit the fields above, stops the command before anything compiles. build and check exit 2, and print the problem as a diagnostic:

CodeWhen
UF7001Node couldn't import the file, or it has no default export
UF7002A field has the wrong shape, or isn't a field the config has
UF7003A name isn't a built-in target. Where one is close, the message suggests it
UF7004A target's options don't pass its schema, or the target declares no options

UF7001 names the file. The others name the field by its JSON pointer, since the problem is in a value, not at a line. A misspelt target reads like this:

error[UF7003]: The configuration at "/targets/2" names the unknown target "svelt". Did you mean `svelte`?
 --> unframework.config.ts:1:1

The decision behind this page is ADR-0076.

Copyright © 2026