Reference

Diagnostics

Every diagnostic the Unframework compiler reports, by its stable UF code, with what it means and how to fix it.

The compiler reports every problem it finds as a diagnostic, with a stable code such as UF3013. A code is never reused or renumbered. The compiler never copies code it has not analysed into an output, so anything it cannot lower to every target is a diagnostic, never a silent difference.

A diagnostic has a severity, a message that names what was found, the place in the source, help, and, where the rewrite is mechanical, fixes. Only an error stops a component from compiling. A fix is safe when it can be applied without a review, and likely when it needs one. The compiler links each code to its section on this page.

This file has no key on its list item:

TagList.uf.tsx
// UF3013 missing-key: a list item without `key`; the likely fix keys it by its index.
export interface TagListProps {
  tags: string[];
}

export default function TagList({ tags }: TagListProps) {
  return (
    <ul>
      {tags.map((tag) => (
        <li>{tag}</li>
      ))}
    </ul>
  );
}

The compiler reports it, with a likely fix:

diagnostics.json
[
  {
    "code": "UF3013",
    "severity": "error",
    "message": "A list's element needs a `key`: the frameworks match each item's element across renders by it.",
    "file": "diagnostics/missing-key/TagList.uf.tsx",
    "start": {
      "line": 10,
      "column": 10,
      "offset": 254
    },
    "end": {
      "line": 10,
      "column": 12,
      "offset": 256
    },
    "help": "Key it by something unique and stable in the item: `key={item.id}`.",
    "fixes": [
      {
        "title": "Key the element by its index, `key={index}`",
        "confidence": "likely",
        "edits": [
          {
            "span": {
              "start": 238,
              "end": 238
            },
            "text": ", index"
          },
          {
            "span": {
              "start": 256,
              "end": 256
            },
            "text": " key={index}"
          }
        ]
      }
    ]
  }
]

Codes are grouped in bands:

BandCovers
UF1xxxSyntax and components
UF2xxxSetup and macros
UF3xxxJSX and cross-component usage
UF4xxxPortability (capabilities)
UF5xxxStylesNo codes yet
UF6xxxTypesNo codes yet
UF7xxxConfig and toolingNo codes yet
UF8xxxPlugins
UF9xxxInternal

Syntax and components

UF1001 syntax-error

Error. The file is not valid TSX.

The parser could not read the file as TSX. The message names the first problem it met.

Fix the syntax error. Any TypeScript editor shows the same error, because a .uf.tsx file is ordinary TSX.

A raw } or > in JSX text is a syntax error: write &#x7D; or &gt;, which every JSX implementation decodes (none decodes &rbrace;), or a string expression ({"}"}). The fix writes the reference where the file then parses. Where it would not, the } is more likely code after an element that is not closed.

UF1002 unsupported-syntax

Error. The compiler cannot lower this construct yet.

The construct is part of the source language, but this version of the compiler cannot lower it to every target yet. The compiler never copies code it has not analysed into an output, so it reports the construct instead.

Rewrite the code without the construct, or wait for the release that supports it. The message names the construct, and the milestone that lowers it where one does. Some constructs are outside the subset because one target cannot read them: Angular's template language has no block-bodied arrow functions, type assertions, hexadecimal numbers or \u{…} escapes, so expressions keep to what every target reads alike. Where a mechanical rewrite exists (0x10 to 16, { return e; } to e), the fix applies it.

Some attributes wait for a target: a name a target's element types do not declare fails that target's type-check whatever its value (writingsuggestions, commandfor and dirname in React's; popover and closedby in Vue's, which are also the authoring types; ARIA 1.3's aria-description in Vue's and Svelte's), and a bound size or multiple on a <select>, or disabled on an <option> or <optgroup>, decides which option starts selected, which some clients settle before they bind it (form state, M3).

UF1101 no-component

Error. The file exports no component.

A .uf.tsx file holds components: exported PascalCase functions whose last statement returns JSX.

export default function Greeting() {
  return <p>Hello, world!</p>;
}

Export a component, or move framework-agnostic code to a plain .ts module.

UF1102 invalid-component

Error. A component must be a named PascalCase function that returns JSX.

A component is a function whose body is its setup, and whose last statement returns the JSX that is its template:

export default function Greeting() {
  return <p>Hello, world!</p>;
}

The compiler reports a component without a name (export default function () { … }), a generator, one whose last statement does not return JSX, and one that renders nothing (return <></>;).

A component returns an element or a fragment. A conditional or a list (return c ? <A /> : <B />;) belongs inside one: the fix wraps it in <>…</>, which renders the same.

A component is a function declaration. One written as a value, an arrow function or a function expression in a const (export const Card = (props: CardProps) => …), is reported, and still checked as the declaration it would be: the fix declares it as export function Card(props: CardProps) { return …; }, keeping its parameters and body.

It also reports an exported function that returns JSX, as a component does, under a name that is not PascalCase in ASCII letters and digits ([A-Z][A-Za-z0-9]*). JSX reads a tag that starts with a lower-case letter (<card>) as an HTML element, and every target names files after the component (Card.vue, and card.ts on Angular, whose selector is uf-card), so its name keeps to the characters every file system and target reads alike. The fix renames the function where its name is used nowhere else.

UF1103 invalid-export-name

Error. A component is exported under a name that is not an identifier.

Every target writes a component's exports as code, and consumers import a component by name to use it as a JSX tag, so its exported name must be an identifier.

export { Card as "my card" }; // rejected
export { Card as MyCard };     // accepted

UF1104 component-name-collision

Error. Two components' names differ only in case.

Each component compiles to a file named after it: Card.vue, and card.ts on Angular. Two names that differ only in case give the same Angular file and selector, and files that overwrite each other on case-insensitive file systems (macOS and Windows).

Rename one of the components.

UF1201 framework-import

Error. Components are framework-free.

A .uf.tsx component is compiled to every framework, so it cannot import one. The compiler writes each framework's code for you: hooks in React, refs in Vue, runes in Svelte, signals in Solid.

Write the component with the authoring API from unframework instead:

import { ref } from "unframework";

export default function Counter() {
  const count = ref(0);
  // …
}

Setup and macros

UF2001 invalid-props

Error. The props parameter is not in a form every target can declare.

A component takes its props as its only parameter, typed by an object type: either destructured, with optional static defaults, or as one object read as props.x.

export default function Badge({ label, tone = "info" }: BadgeProps) { … }
export function Plain(props: PlainProps) { return <p>{props.label}</p>; }

The compiler reports a parameter without a type annotation, a second parameter, a renamed, nested or computed property in the pattern, a member a props type declares twice, which TypeScript rejects too, a default on a required prop, a default on a prop whose type admits null (unless the default is null: Qwik applies a destructured default to null too), and, in the object form, a use of the object other than reading one of its members (props, props[key], props.x = …, (props).x) and a name starting with $$: Astro's compiled component declares $$props, $$result, $$slots and $$render where its output declares the object.

Destructure the props by their own names, declare each once, make a prop with a default optional, and read the object form only through its members.

UF2002 non-static-prop-default

Error. A prop's default must be a static value.

Vue hoists defineProps defaults out of the component and Angular reads an input's default before any input is set, so a default cannot read anything: not a prop, not a global, not a function's result.

A default is a string, number, boolean or null literal, a negated number, a template literal without expressions, or an array or object literal of those (identifier or string keys, no spreads).

({ page = 1, tags = ["new"] }: P)        // accepted
({ pageCount = Number.MAX_SAFE_INTEGER }: P) // rejected: reads a global

Write the value itself, or compute it in the template (pageCount ?? 10).

UF2003 reserved-prop-name

Error. A target reserves this prop name.

Every target declares each prop by its name and reads it in its templates, so a prop's name keeps to ASCII letters and digits (Angular's expression lexer reads no other letter), starting with a letter, and none a target gives a meaning of its own:

  • key, ref, children, class, style, slot, is, ref_for and ref_key, which React, Vue, Svelte or Astro read themselves and never pass to the component;
  • props, rawProps, constructor and Astro, which the outputs declare beside the props, and Fragment, which Astro's output imports to render <>;
  • JavaScript's reserved words, Angular's expression keywords (as, let, typeof, …) and the globals expressions may read (Math, String, …);
  • names of events (onClick: events land in M2), names starting with ng and a capital letter (ngIf), which Angular reserves for its directives, and names ending in $, which Qwik reads as a QRL.

The outputs also declare types named Props and CSSProperties, Angular's output imports its Component decorator, which is also a type, and reads TypeScript's Exclude, and Solid's output reads TypeScript's Partial, Record, Required and Pick, so a local type of any of these names is reported. Props is accepted as a component's own props type, when every component whose props reach it takes it as its props type: Astro's output declares a Props of its own for any other.

Rename the prop or the type.

JSX and cross-component usage

UF3001 unknown-element

Error. The element is not an HTML element.

Lower-case JSX tags are HTML elements, and the compiler emits them unchanged into every target. A tag that HTML does not define, an obsolete one (<center>, <marquee>) or one in the wrong case (<dIV>) would be read differently by each framework: Vue resolves it as a component, Angular rejects it, and the browser renders an unknown element.

Inside an <svg>, tags are SVG elements in SVG's own case (linearGradient, clipPath): an HTML element there, or an SVG element outside an <svg>, is reported too.

Use the HTML or SVG element; a misspelt case has a fix. Components are PascalCase (<Card>).

UF3002 reserved-element

Error. A component cannot render this element.

Some elements belong to the document or to a framework's template syntax, not to a component's markup:

  • <html>, <head> and <body> are the document's; the browser drops them inside one.
  • <title>, <meta>, <link> and <base> describe the document: React moves them into the <head>, and the other frameworks leave them in place.
  • <script> and <style> hold code the compiler cannot analyse; Vue and Angular drop them. A component's styles are a stylesheet it imports. This holds inside an <svg> too.
  • <template>, <slot>, <component>, <transition> and Angular's <ng-…> elements are template syntax in one framework or another.
  • SVG's animation elements (<animate>, <animateMotion>, <animateTransform>, <set> and <discard>) make the rendering depend on time, and <animate> and <set> can set any attribute, a javascript: href included.

UF3003 invalid-nesting

Error. The browser or a framework would repair this markup.

The HTML parser repairs some nesting: a <div> closes an open <p>, a <tr> directly in a <table> gets a <tbody>, text in a table moves out of it, and a void element such as <br> cannot hold children. Server-rendered HTML then becomes a different tree from the one a client renderer builds, and Svelte, Vue and React each report it. Svelte's compiler also drops whitespace on its own inside a <select> or a <datalist>.

The rules see through conditionals, lists and fragments: rows that a .map renders directly in a <table> get a <tbody> too. An expression renders text, so it cannot sit where text cannot (a table part, a <select>), and inside an <svg> only <text>, <tspan>, <textPath>, <title> and <desc> render text. Svelte drops whitespace-only text in SVG outside a <text>, so it is reported there too. SVG's <title> and <desc> hold text only: the HTML parser and Vue read an element inside them as HTML, and React as SVG.

The parser reads an <iframe>'s content as raw text, decoding no character reference and reading no comment, so text other than whitespace, an expression or a conditional there renders the servers' escapes and markers as text, which a client render does not. A browser never shows that content: remove it.

Write the markup the parser keeps: wrap rows in <tbody>, use a <div> where a paragraph would contain blocks, and put options on lines of their own.

UF3004 non-canonical-attribute

Error. The attribute is not written in its canonical form.

Each attribute has one way to be written (P3):

  • HTML names, in lower case: class, for, tabindex, not className, htmlFor or tabIndex.
  • A boolean attribute without a value: disabled, not disabled="" or disabled="disabled". Its presence turns it on: disabled="false" is still disabled.
  • A value for every other attribute: in JSX a bare attribute means the string "true", which is only meaningful for ARIA states, data-* and true/false attributes.
  • Numbers in canonical form (3, 0.5): some renderers set these attributes as DOM properties, which rewrites any other spelling.
  • No class rather than an empty one: Vue renders class="", the others leave it out. (Whitespace between class names needs no fix: the compiler writes one space.)
  • A literal as a literal: title="x", not title={"x"}; disabled, not disabled={true}; no attribute rather than {false}, {null} or {undefined}; class="a b", not class={["a", "b"]}; the attributes themselves, not a spread of an object literal.
  • Style keys in camel case (marginTop), as React's and Vue's style objects write them.
  • href, not SVG's old xlink:href.
  • key, in lower case, on a list's element: in JSX KEY is an attribute.
  • No spread that renders nothing: of a type that declares no keys, or of a source that a condition around the spread shows is absent ({attrs ? null : <p {...attrs} />}).
  • An enumerated attribute's keyword in lower case (dir="rtl", not dir="RTL"), and the keyword HTML reads an empty value as (spellcheck="true", popover="auto", a <button>'s type="submit"): the targets' types take only those spellings.

The fix rewrites the attribute where the rewrite is mechanical.

UF3005 reserved-attribute

Error. The attribute belongs to a framework.

The attribute is syntax or data of one of the target frameworks, so that framework would not render it as an attribute:

  • Angular binds bind-*, on-*, bindon-*, ref-* and let-*, consumes i18n and reserves ng* names.
  • slot and is are template syntax in Svelte, Astro and Vue.
  • children, dangerouslySetInnerHTML, textContent and React's suppress… props are framework props, not attributes; write the content as JSX children.
  • data-hk, data-astro-*, data-v-*, data-qwik-* and data-uf-* are attributes that frameworks or the compiler add themselves, and ids starting with uf-id- are the ones the compiler generates.
  • xmlns, xmlns:*, xml:* and xlink:* (but xlink:href, which is href) are XML's: every target writes SVG's namespace itself, and the HTML parser and the clients disagree about namespaced attributes.

UF3006 unknown-attribute

Error. The attribute is not an attribute of this element.

Elements take HTML's attributes: the global ones (id, class, title, hidden, …), their own (href on <a>, alt on <img>), ARIA's role and aria-*, and data-*. SVG elements take SVG's attributes, in SVG's own case (viewBox, stroke-width). Frameworks treat other names inconsistently: Vue sets a name that matches a DOM property as that property, and React and Solid give some names meaning of their own.

Use the element's attribute, or a data-* attribute for your own data.

UF3007 duplicate-attribute

Error. The element has the same attribute twice.

Attribute names are case-insensitive in HTML, and className is class. With the same attribute twice, React keeps the last value, the HTML parser the first, and Vue and Svelte fail to compile. Keep one.

A spread sets every key its type declares, so a key that is also written on the element, or set by another spread, is set twice too. class is the exception: one spread's class merges with the element's own. A class name listed twice in one class is reported as well: Angular merges a static class with a bound one through the class list, which drops the repeat the other targets keep. The fix lists a static class's names once. So is a second key on a list's element: the list has one key.

UF3008 unportable-attribute-value

Error. The targets would render this attribute value differently.

Some values cannot be rendered the same way on every target, and some hold code or a document the compiler cannot analyse, which it never copies into an output:

  • javascript: URLs run code the compiler cannot analyse, and React blocks them. Event handlers are the way to run code.
  • srcdoc holds an HTML document, scripts included, which the frame runs as the page's own origin unless it is sandboxed. A data: URL that a frame loads (an <iframe>'s or an <embed>'s src, an <object>'s data) is a document too. Load the document from a URL, or write its markup in the component. data: images and media, and blob: URLs, hold no code the page runs, and are accepted.
  • An empty src, or an empty href outside <a>: React drops it and warns.
  • hidden="until-found": React renders hidden as a boolean.
  • A number out of range, or not a number, in a numeric attribute (rows, span, <li value>): renderers that set the DOM property rewrite or reject it.
  • A class name holding whitespace other than ASCII's (a no-break space): Angular splits class names there, and the other targets keep it in the name.
  • An attribute that React and Qwik type as a number (tabindex, colspan, aria-level) with a value that is not one: they write it as a number literal.
  • An ARIA value ARIA does not define (aria-hidden="yes", aria-live="loud"), or a role that is not one of ARIA's roles: assistive technology ignores it, and Svelte warns about it.
  • A value of an enumerated attribute that some target's types do not list (a <bdo>'s dir="auto", role="generic", autocomplete="bday", a <button>'s type="menu"): that target's output fails its type-check. The message lists the values every target takes.

UF3009 ambiguous-jsx-text

Error. JSX implementations read this text differently.

JSX does not define its text exactly, and its implementations disagree. Babel (which Vue's and Solid's JSX use) decodes character references first and then trims the lines, trimming only spaces and tabs. TypeScript, oxc and esbuild trim the raw lines first, also trimming Unicode whitespace such as a no-break space, and read U+2028 and U+2029 as line breaks. So &#32; at the end of a line, &#10;, a tab, or a raw no-break space next to a line break means one thing to some tools and another to the rest.

The compiler lowers text only where every implementation agrees. Write such characters as references (&nbsp;), write a space for a tab, and keep a significant space on the same line as the text it separates. Numeric references the implementations decode differently (&#+65;, a surrogate such as &#xD83D;, a number past U+10FFFF) are reported too, in attribute strings as well.

UF3010 unportable-character

Error. HTML would not keep this character.

Each target renders the component's markup on the server as HTML, and the browser parses it, so a static value must hold only characters the HTML parser keeps as they are:

  • a carriage return becomes a line feed (Svelte's compiler converts it too); in a file with CRLF line endings, a value written across lines holds one;
  • NUL (U+0000) is dropped from text and replaced in attribute values;
  • a lone UTF-16 surrogate cannot be encoded in UTF-8;
  • the control characters other than whitespace, and the noncharacters, are not allowed in an HTML document.

Remove the character. A carriage return from a line ending has a fix.

UF3011 html-only-reference

Warning. JSX does not decode this HTML character reference.

JSX decodes only the named character references of XHTML 1.0 (&amp;, &nbsp;, &copy;), and numeric ones (&#x2713;). The names HTML added later (&check;, &rbrace;, &NewLine;) stay as written: every target renders &check; as those seven characters, where an HTML or Vue template would show ✓.

Write the numeric reference for the character (the fix, where it changes nothing else), or &amp;check; for the text itself.

UF3012 jsx-outside-template

Error. JSX can only be in a component's template.

A component's template is the JSX its last statement returns, with the slot functions in it. The compiler lowers that tree to each target's template, and Vue's, Svelte's and Angular's templates are not JSX: they have no counterpart for JSX kept in a variable, returned from a helper, passed as a prop that is not a slot, or used as a value (an attribute's value, a function's argument, an array's element). JSX can be a child, a branch of a conditional child (c ? <A /> : <B />, c && <A />) or the element a .map child renders.

function renderIcon() {
  return <i class="icon" />; // rejected: a helper that returns JSX
}

Inline the JSX where it is used, move it into a slot function, or extract a component.

UF3013 missing-key

Error. A list's element needs a key.

A .map child renders one element per item, and the frameworks match the elements of two renders by their key. Without one, React warns and the targets disagree about which element an item keeps.

{items.map((item) => <li key={item.id}>{item.name}</li>)}

Key the element by something unique and stable in the item. The fix keys it by its index, which is unique but moves with the item: review it.

UF3014 misplaced-key

Error. A key belongs on the element a list renders.

key is not an attribute: it tells the frameworks which item of a list an element renders, and only the element a .map callback returns has one. Elsewhere React and Vue use it to remount an element and the others ignore it. A constant key on a list's element gives every item the same key.

The fix removes a misplaced key, and keys a list's element by its index.

UF3015 invalid-list

Error. A list is written source.map((item, index) => <element key={…}>…</element>).

A .map child lowers to each target's list: Vue's v-for, Svelte's {#each}, Angular's @for. Its callback is an arrow function with one or two plain parameters, the item and its index, that returns one element:

{users.map((user, index) => (
  <li key={user.id}>{index + 1}. {user.name}</li>
))}

Read an item's members as item.name rather than destructuring it (an entry of Object.entries as entry[0] and entry[1]), and wrap several elements in one. An item that renders or not is filtered out before mapping (users.filter((user) => user.active).map(…)); one that renders one element or another keeps one keyed element that holds the conditional (<li key={link.id}>{link.href ? <a …>…</a> : <span>…</span>}</li>).

UF3016 non-text-interpolation

Error. An expression child must render text.

An expression child renders its value as text. JSX renders true, false and arrays its own way, and the template targets render them as text ({{ true }} is "true" on Vue), so an expression child must be a string or a number, or null or undefined, which render nothing.

Turn the value into text: done ? "Done" : "Open" for a boolean, .join(", ") or .map(…) for an array, a member for an object. A literal {true} or {false} child renders nothing: remove it.

UF3017 dropped-line-feed

Error. The HTML parser drops a line feed at the start of this element.

The HTML parser drops a line feed that starts a <pre>, a <textarea> or a <listing>, and React's server renderer writes one more to keep it. Server-rendered HTML and a client render then disagree about the first line.

A text after a conditional or a list that can render nothing starts the element when it does: React's and Astro's servers then write nothing before it, so the parser drops its line feed, and the other targets write a comment, which keeps it.

Start the element's text with something other than a line feed.

UF3018 unportable-binding

Error. The targets render or type this bound value differently.

A bound value renders by the rules of where it is bound, and the targets agree only for some kinds of value:

  • a boolean attribute (disabled) takes a boolean; ARIA's true/false states (aria-hidden), draggable and spellcheck also take booleans, as "true" and "false";
  • an attribute some target's types restrict to a list of tokens (autocomplete, inputmode, role, aria-live, SVG's fill-rule) takes a string literal, or a union of them, that every target's types accept: the diagnostic lists them;
  • data-* and other attributes take strings and numbers: Qwik drops false there, React drops it on some, and the others write "false" (write String(value));
  • an attribute React and Qwik type as a number (tabindex, colspan) takes a number, and one some target's types declare as a string (title, id, aria-label, SVG's d) takes a string;
  • a class name takes a string, a style value a string, or a number where CSS needs no unit (opacity, line-height): React and Qwik add px to other numbers but 0, and Qwik also to SVG's stroke and opacity properties (stroke-width), which React leaves bare. A literal 0 is the value 0 on any property;
  • a list's source is an array, and its key a string or a number that reads the item or its index: a key that reads neither is the same for every item, which Vue and Svelte reject. It identifies the item within its own list, so it reads no variable of a list around it: Angular's track reads only its own item, $index and the component's members. A source that can be absent is defaulted, (items ?? []).map(…), which the fix writes for items?.map(…).

null and undefined leave the attribute or the declaration out everywhere, but for a value some targets set as a property (an <li>'s, a <meter>'s, an <option>'s: UF1002). Convert the value, or bind it where its kind renders alike.

UF3019 nondeterministic-render

Error. Rendering must not depend on time, chance or the machine.

A component renders on the server and again in the browser, and both renders must give the same markup (plan §4.5): rendering is a function of props, state and slots. Date, Intl, crypto, performance, globalThis and Math.random read the clock, chance or the machine, and toLocaleString, toLocaleDateString, toLocaleTimeString, toLocaleUpperCase, toLocaleLowerCase and localeCompare read the machine's locale.

Pass the value in as a prop, computed where it is decided.

UF3020 unresolved-reference

Error. The name is no prop, list item or global an expression can read.

An expression in the template reads the component's props, the item and index of the lists around it, the parameters of the arrow functions in it, and a few pure globals: undefined, NaN, Infinity, Math, Number, String, Boolean, Array, Object, JSON, parseInt, parseFloat, isNaN, isFinite and the URI functions. The targets can reach nothing else: Angular's templates see only the component's members, and the outputs run where window may not exist.

Declare the value as a prop, or check the name's spelling.

UF3021 impure-render-expression

Error. A template expression must not change anything.

Rendering is a pure function of props, state and slots (plan §4.5): a framework renders when it decides to, sometimes twice, and Svelte throws when a template expression changes state. So a template expression cannot assign, update or delete, use this, await, yield, void or the comma operator, define a function or a class, or call a method that changes its receiver: sort, reverse, splice, push, pop, shift, unshift, fill, copyWithin, and Object.assign and its kin.

Use the methods that return a new array: the fix writes toSorted, toReversed and toSpliced. An arrow function is a call's argument (items.filter((item) => item.on)).

UF3022 invalid-class-or-style

Error. The class or style is malformed.

A class and a style are lowered part by part, and every target writes the parts its own way, so each part must be one the targets read alike:

  • class and style objects have plain keys: no computed keys, spreads or methods;
  • a style string parses as CSS declarations, and a style sets something;
  • each property is one the browsers know (or a custom property, --gap): an unknown name is almost always a typo, which no browser applies and Solid's lint rejects;
  • a custom property's name is in lower case: Angular's compiler and its server renderer lowercase it, so var(--myGap) would not find --myGap there;
  • a style sets each property once, and never a shorthand beside one of its longhands (margin and marginTop), or a flow-relative property beside a physical one it can be (marginInlineStart and marginLeft): React's, Solid's and Qwik's style objects cannot keep the order that decides the result;
  • no !important, which React's style objects drop;
  • a static value reads the same to Angular's style parser, which knows no escapes or comments: a string holds no escaped quote of its own kind (put the other quote around it), the parentheses inside strings balance (write \28 for a lone one), and a comment holds no quote, parenthesis or ;.

Write each property once, with its final value.

UF3023 non-nullable-operand

Error. The operand of ?? or ?. is never null or undefined.

a ?? b and a?.b act only when a is null or undefined. When a can be neither where it is read, the operator does nothing, and Angular's compiler rejects it (NG8102, NG8107). a can be neither when its type excludes both, or when a condition around it rules them out as TypeScript narrows it: a test of a itself, through !, && and ||, a comparison with null, undefined or a value, typeof, a member read through ?., Array.isArray, or a discriminant of the union a belongs to:

{box.inner && <p title={box.inner?.title}>…</p>} // reported: write `box.inner.title`

Remove the operator, which the fix does, or make the prop optional if it can be absent.

Where the targets' checkers may read a apart, the compiler reports, as not supported yet (UF1002), a use that relies on a narrowing some of them do not see: a member, or a prop of the object form, a condition narrows outside a list's callback (Angular's loops keep the narrowing, the JSX targets' callbacks forget it); a prop an expression's own conditional narrows, used in an arrow function in it (Solid copies it with props.x, which the closure forgets); and a prop a list's key uses (Angular's track reads the input again). Testing the value, reading it through ?., giving it a value with ??, comparing it for equality and writing it into a string read alike everywhere. Where the compiler does not follow a test (an equality with a value that is no literal), Angular may narrow a and reject the operator, which is reported too. A destructured prop a conditional child narrows stays narrowed in its branch's lists and arrow functions on every target. Reading a member of a value a condition shows absent is reported as well.

UF3024 shadowed-binding

Error. A parameter in the template shadows a name the outputs read, or is never read.

The targets rewrite the names a template expression reads: Solid reads a prop label as props.label, and Angular reads the template variable it declares for it, @let label = this.label();. A .map or arrow function parameter that has the name of a prop, of the object form's props parameter, of the item or index of a list around it, of a global an expression may read (String), the name props or rawProps, a name starting with $ (Angular's @for declares $index, $count and others), Fragment (Astro's output imports it to render <>) or a name starting with _ but _ itself (Vue's compiled render functions declare _ctx, _cache, __props and helpers such as _toDisplayString) would capture those rewrites or those names. A parameter named after one of Angular's expression keywords (as) cannot be read in its templates at all. Rename the parameter.

An arrow function's parameter that nothing after it reads fails the outputs' lint (no-unused-vars). The fix removes it. (A list's unread index needs no fix: the targets leave it out.)

UF3025 unsupported-conditional

Error. A conditional child is written c ? <A /> : <B /> or c && <A />.

x || <B /> and x ?? <B /> render either the value of x or an element, which no template target can express as one conditional. Write the conditional with ?:: x ? x : <B /> for ||, and x != null ? x : <B /> for ??, which renders 0 and "" as ?? does.

{message ? message : <p>All good.</p>}
{count != null ? count : <p>None yet.</p>}

When x is a prop or a member of one that renders as text, the fix writes the conditional.

Portability (capabilities)

UF4001 unsupported-capability

Warning. A selected target cannot support a feature the component uses.

Each target declares, for every feature, whether it supports it natively, emulates it with an inline helper, or cannot support it. The component uses a feature that a selected target cannot support; the diagnostic points at its first use, and the message names the feature and the target's reason. For example, Astro renders on the server only, so event handlers are inert there.

A feature can also be a construct one framework renders differently from HTML and the other targets. Vue's client, for example, selects the first option of a single-selection list box (<select size="2"> without multiple), where HTML selects none, so Vue declares listbox unsupported, and the diagnostic points at the list box's size.

The severity comes from the target's capability matrix. With strict: true, portability warnings are errors. Remove the feature, or drop the target.

Plugins

UF8001 plugin-failed

Error. A compiler plugin failed.

A compiler plugin's ir or output hook threw, or returned something other than valid IR or a list of files. Valid IR has the schema's shape and keeps the invariants every target relies on (checkInvariants): component and export names every target can write, HTML every target renders alike, and no code or document the compiler cannot analyse. The message points at the first problems by JSON Pointer.

The compiler reports it instead of crashing, and leaves the plugin's step out: hooks receive a frozen module and frozen files, so a hook changes them by returning new ones, and a failed hook leaves nothing behind.

Fix the plugin, or remove it from the configuration.

Internal

UF9001 internal-error

Error. The compiler failed unexpectedly.

This is a bug in Unframework. Please open an issue with the file that triggered it: https://github.com/uxfront-com/unframework/issues/new/choose

Copyright © 2026