Add Angular framework support to Ark UI#3888
Open
ryan-mahoney wants to merge 453 commits into
Open
Conversation
…ch-5-components Implement Angular Batch 5 components
…lities Complete Angular Batch 6 utilities
Align Angular stories with React parity
Add missing Angular components and align stories
|
@ryan-mahoney is attempting to deploy a commit to the Chakra UI Team on Vercel. A member of the Team first needs to authorize it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add Angular framework support (
@ark-ui/angular)First — thank you to the Chakra/Ark UI team. This work would not exist without
the careful design of Zag.js, the consistent component anatomies you've
established across React/Solid/Svelte/Vue, the headless API surface, and the
documentation site that made it possible to verify behavior against a known
reference.
This PR contributes a fifth framework target — Angular — modeled directly on
the existing framework packages and the patterns documented in
CLAUDE.md/.claude/docs. It is offered as a starting point: I expect the maintainerswill want to reshape parts of it (entry-point layout, adapter location,
naming) to match Ark's conventions, and I'm happy to follow that lead.
TL;DR
packages/angularworkspace publishing@ark-ui/angular, mirroringthe React/Solid/Svelte/Vue packages in API parity and feature coverage.
machines (same versions as the other framework packages —
@zag-js/* 1.40.0).story names and example identifiers used by the React reference so the
website's example registry can resolve them by ID.
@analogjs/vite-plugin-angular(zoneless), with coverage of the adapter, the form integration, and per-
component behavior.
llms-angular.txtroute, example-registry plumbing, type-doc generation(
scripts/src/generate-type-docs.angular.ts), andtypes/angular/*.jsonfor the components covered so far.
packages/mcp) lists Angular among supported frameworks.Scope of changes
packages/angular/**website/**(non-asset)scripts/**packages/mcp/**README,package.json, turbo wiring)The work landed across ~326 conventional commits (
feat(angular),fix(angular),docs(angular),test(angular),chore(angular)) so thehistory is fully bisectable. The branch can be reshaped into smaller staged
PRs on request — see "How to land this" below.
What's covered
Components (52)
accordion,angle-slider,avatar,carousel,checkbox,clipboard,collapsible,color-picker,combobox,date-input,date-picker,dialog,download-trigger,drawer,editable,field,fieldset,file-upload,floating-panel,hover-card,image-cropper,json-tree-view,listbox,marquee,menu,navigation-menu,number-input,pagination,password-input,pin-input,popover,progress,qr-code,radio-group,rating-group,scroll-area,segment-group,select,signature-pad,slider,splitter,steps,switch,tabs,tags-input,timer,toast,toggle,toggle-group,tooltip,tour,tree-view.Utilities / providers (14)
client-only,collection,focus-trap,format(byte/number/relative-time/time),
frame,highlight,portal,presence,swap,download-trigger, plus the three providers —providers/environment,providers/interaction,providers/locale. Each utility has parity examplesand a Storybook story matching the React reference.
Not yet covered
I matched the React surface as closely as possible but there are a couple of
React-only components I deliberately did not port pending a maintainer call:
time-picker(React has type docs but the component is still gated inReact's surface area)
mainI'm happy to add these in a follow-up once direction is settled.
Architecture
The package follows the same headless / part-directive model used by the other
framework packages. The translation from "React components with a shared
useMachinehook" to "Angular directives with a shared injector-scopedservice" is the only place where the implementation meaningfully diverges from
its siblings. Three small files in
src/_zag/do this work:src/_zag/use-machine.ts(~525 LOC)A signal-based adapter that mirrors
@zag-js/react'suseMachine. Highlights:@zag-js/corehelpers (createScope,findTransition,getExitEnterStates,hasTag,matchesState,resolveStateValue) — no custom state machine logic.signal()for the bindable storecomputed()for derived props/apieffect()for context tracking, deps tracking, and late-arrivingdefaultValuehydrationinject(DestroyRef)for cleanupstate.invoke(INIT_STATE)untilafterNextRender(). This was thesubtle one: machines like
floating-panel/signature-padresolvedescendants in their entry actions via
scope.getById, and the partdirectives'
applyArkPropseffects need to have flushed first. Thismirrors the
useSafeLayoutEffect + queueMicrotaskpattern Zag uses onReact.
createBindablehonors the React convention thatundefinedmeansuncontrolled and
nullis a deliberate controlled value (Progress'sindeterminate state depends on this).
don't churn the machine.
src/_zag/apply-ark-props.ts(~269 LOC)The
props().getRootProps()/getItemProps()etc. objects returned by Zagare normalized React-style prop bags. This file applies them to a host
element via
Renderer2:isAttributeKey(role/id/tabindex/aria-/data-)
onChange→ correct DOM event name based on tag/input typeclassName/classmerged additively (preserves user-authored classes)stylewith first-class CSS custom property support viasetPropertyDestroyRefsrc/_zag/normalize-props.ts(5 LOC)Identity normalizer; Zag's React-shaped prop bags are what we want.
Part-directive pattern
Each part is a standalone Angular directive that injects the root's context
via DI, reads its prop bag from the connected api, and forwards it through
applyArkProps. Example:[arkAccordion],[arkAccordionItem],[arkAccordionItemTrigger], etc.Forms integration (
@angular/forms)Form-bearing components (combobox, checkbox, listbox, number-input, pin-
input, radio-group, rating-group, select, switch, tags-input,
toggle, file-upload, slider, segment-group, password-input, editable, field,
fieldset) implement
ControlValueAccessorthrough a sharedcreateArkCvaControllerhelper.@angular/formsis declared an optionalpeer dependency so consumers who don't use Reactive/Template-driven forms
aren't forced to install it.
A small dev-time guard (
scripts/check-forms-isolation.ts) enforces thatnon-form entry points never import
@angular/forms. It runs as part ofbun run typecheck. The matchingcheck-forms-isolation.spec.tskeeps theguard honest.
Public API shape
Component subpath exports follow the existing convention (
@ark-ui/angular/ accordion, etc.). The package ships as an Angular Package Format (APF)library via
ng-packagr20.x, producing FESM2022 bundles and.d.tsdeclarations.
Two private subpaths exist for the adapter (
./src/_zag,./src/internal).scripts/hide-private-entrypoints.tsruns after buildand rewrites the published
package.jsonso consumers can't import them.This was the cleanest way I found to share types between secondary entry
points while keeping the consumer surface clean — I'm open to a different
arrangement if the maintainers prefer.
peerDependencies:@angular/common:^20.0.0@angular/core:^20.0.0@angular/forms:^20.0.0(optional)Angular 20 was chosen because it's the first release where signal-based
inputs (
input(),model(),output()) and the zoneless renderer arestable — both are used heavily in the adapter and component directives.
Examples & stories
Every component has at least the same parity examples as the React reference
(
basic,controlled,disabled,multiple, etc.) plus framework-specificvariants where they made sense. Each example directory mirrors React's:
The story registry generator (
scripts/src/generate-example-registry.ts) wasextended to pick up these examples so the website can render them via the
existing framework-switcher.
A small batch of commits (
fix(angular): align <component> story with react parity) walks through every story and reconciles visual diffs against theReact reference — these were each verified in the running Storybook.
Tests
_zag/use-machine.spec.ts,_zag/apply-ark- props.spec.ts,_zag/normalize-props.spec.tsinternal/{id,split-props,render-strategy,context- carrier}.spec.tsforms/control-value-accessor.spec.ts+scripts/check-forms-isolation.spec.ts<component>.spec.tsfor every component (79 specfiles total). These exercise the connected api, key interactions, and the
state contract — not styling/visual.
Test runner: Vitest 4 with
@analogjs/vite-plugin-angularand a zonelesstest setup. This matched the rest of the monorepo's preference for Vitest.
Website / docs / MCP
website/src/lib/frameworks.tsframeworkstuple now includes'angular'website/src/components/navigation/framework-select.tsxwebsite/src/app/(llms)/llms-angular.txt/route.tswebsite/src/lib/angular-example-registry.tswebsite/src/lib/angular-prop-binding.tswebsite/src/lib/types-angular.test.tswebsite/src/content/types/angular/*.jsonwebsite/src/content/pages/utilities/*.mdxwebsite/src/content/pages/overview/getting-started.mdxwebsite/src/content/pages/ai/llms.txt.mdxpackages/mcp/src/lib/types.tsangularadded to supported framework unionscripts/src/generate-type-docs.angular.tsThe type-doc generator is structured to match the React generator's output
format byte-for-byte for the same component, so the existing
<PropsTable>component renders Angular's props with no special-casing.
How to land this
This branch is large by necessity (a new framework target) but I've tried
to keep commits atomic. If staged landing is preferred, here is a natural
split:
packages/angular/src/_zag,internal,workspace wiring, build/test/lint config). No components. Verifies the
adapter in isolation.
forms and non-forms parts and a small surface). Validates the
end-to-end pattern.
roughly batches 1–6 in the merged commits; can be replayed as separate
PRs).
frameworkstuple, framework select, typedocs generator + content).
I'm happy to either rebase into that shape, or land this as-is and address
review feedback in follow-up commits — whichever fits the maintainers' usual
workflow.
Open questions for maintainers
A few decisions I made one way but would happily revisit:
packages/angular/src/_zagrather than spinning up
@zag-js/angular. That kept the change insidethis repo and avoided coordinating with the Zag repo, but if you'd
prefer the adapter live alongside
@zag-js/reactetc., I'll extract it.(
packages/angular/avatar/) and others undersrc/— this happenedbecause of how
ng-packagrsecondary entry points resolved duringincremental development. I can normalize to a single layout.
makes the form-bearing components slightly more involved to consume.
Open to alternatives.
[arkAccordion]etc. for directives; if Ark'smaintainers prefer
ark-accordionelement selectors or a differentprefix, that's a mechanical rename.
Local verification
The
@ark-ui/angularpackage builds cleanly through ng-packagr to the APFmanifest in
dist/; both thesourceanddefaultexport conditionsresolve correctly for workspace consumers and published artifacts.
Thanks again for considering this. The Ark UI architecture made the
framework port genuinely pleasant — most components were a near-mechanical
translation from the React equivalents, which is a credit to how well the
Zag/Ark boundary is drawn. Let me know how you'd like to proceed and I'll
follow up.