CLI release notes

What changed in each release of delacour, from the changesets that shipped it.

Every version of delacour. The CLI that copies components into your app. It shares one version with the other Delacour packages — see Release channels.

0.8.0

GitHub release

Minor changes

  • #335 The CLI now sends anonymous usage telemetry, on by default: the command, the names (never the values) of the flags typed, the components asked for from the official registry, the outcome and duration, the package manager and Expo SDK, and the OS, runtime and CI or coding-agent context, to telemetry.delacour.co.nz. It never sends source code, file names or paths, remote URLs, error messages or an IP address. A notice prints on first use. Turn it off with delacour telemetry disable, DO_NOT_TRACK=1, DELACOUR_TELEMETRY_DISABLED=1 or DELACOUR_CLI_TELEMETRY_DISABLED=1; DELACOUR_TELEMETRY_DEBUG=1 prints what would be sent instead. New delacour telemetry [status|enable|disable] command, documented at /docs/native/cli#telemetry.

0.7.0

GitHub release

Minor changes

  • #327 native-components.json records a version. init writes the current one, and update brings an older config up to it first: at a terminal it asks once about each option the file predates (tests.enabled, and prefix while nothing is copied yet), then records the answers and the version. --yes keeps the defaults; --json, a pipe, CI and the MCP server leave the file alone and name what is still to choose. doctor warns on a config that is behind, or written by a newer CLI. The docs gain an Updating guide.

  • #327 init asks what components should call the library in place of DelacourUI (--prefix <name> on init or the first add answers it), and update asks a project that has no prefix even after components are copied: answering renames it through every copy, merging into edited files, and lists the project's own files that still import DelacourProvider. A prefix set by hand in native-components.json is renamed through on the next update the same way.

  • #327 When add or update copies tests, it adds test:ui (every copied suite), test:ui:unit (the logic suites) and, on Jest, test:ui:render (the render tests) to package.json, scoped to the component directories so the project's own test script is untouched. A script already under one of those names is kept as it is.

  • #327 update lists each component it would change — every file it updates, adds or merges, with a line count — and asks which to take; an unticked component is left for the next run. add and update now ask which missing packages to install, one checkbox each, instead of all or nothing.

    update no longer adds a component the project does not hold unless the registry introduced it since the copy: a dependency the project removed (an icon set swapped for another) is reported and offered unticked, and a test that renders a component the project lacks is left out instead of copying that component in.

Patch changes

  • #327 update no longer reports a merge as clean when the project and the registry each added the same line in different places. The merge used to keep both — a duplicate parameter in a file that then failed to parse — and now writes the region as a conflict to resolve.

  • #327 add and update install a package again at the library's range when package.json has it below that range — a project on tailwind-variants 3 now gets 4, which the copied lib/tv.ts imports from. The report names each one and the version it found. update also reads an old ref out of git when the registry is a local directory, so a linked CLI merges an edited file instead of skipping it and copying newer tests beside the old source.

  • #327 DELACOUR_REGISTRY names the registry to read when neither --registry nor registry.url in native-components.json does — a path, a URL or github:owner/repo. Empty means the published registry.

  • #327 The test kit no longer mocks native modules the project may not have. Skia, Pulsar, MapLibre, expo-linear-gradient and expo-blur each have a mock of their own under test/mocks/, copied only with a component that installs the module and added to Jest's setupFiles — so a project without chart no longer fails every suite with "Cannot find module 'react-native-skia'".

  • #326 update no longer crashes with EPIPE when the project's formatter exits before reading its input; the file is compared unformatted and the failure reported once.

0.6.0

GitHub release

Minor changes

  • #289 Add prefix to native-components.json: add and update rename every DelacourUI display name and thrown-message tag to it, and DelacourProvider to <prefix>Provider — "prefix": "UI" gives UI.Flow.Provider and UIProvider. init and doctor name the provider the same way.

    add now installs the plain npm packages the components use (tailwind-variants, tailwind-merge, cn, tailwindcss, the icon set) at the ranges @delacour/react-native-ui declares, instead of npm's latest. tailwind-variants 4.0.0 broke every copied *.variants.ts.

  • #311 Add component render tests with @testing-library/react-native under Jest. delacour add --tests now copies a component's render suite, the Jest setup that stubs its native modules and a renderWithProviders helper into a Jest project, adds the setup to the project's Jest config, and remembers the choice in native-components.json. Fix a disabled Switch flipping on a screen reader's activation or its Field label press.

  • #291 Upgrade tailwind-variants to v4. lib/tv now builds tv with createTV from tailwind-variants/config, the only v4 entry that accepts twMergeConfig — the default entry throws on it, so a tv.ts copied by an earlier CLI breaks as soon as v4 is installed. tailwind-merge is no longer a dependency: v4 ships its own compiled merge tables. delacour add stops installing it.

    v4's types drop numeric variant keys, so KpiVariantProps["colorIndex"], PlannerVariantProps["colorIndex"] and LeaderboardVariantProps["place"] are now string unions ("1" | "2" | …). The component props — KpiColorIndex, PlannerColorIndex, LeaderboardPlace — stay numeric.

Patch changes

  • #316 Copied tests now typecheck on TypeScript 6. add --tests prepends a /// <reference types> for the runner's ambient types (bun, or node under Jest and Vitest), since TypeScript 6 — what Expo SDK 58 ships — defaults types to [] and every copied suite failed tsc in a fresh app.

  • #312 doctor warns when a Jest project using chart has no transform for .mjs. d3-shape 3.2.1 ships only .mjs, which jest-expo's transform does not compile, so every suite reaching chart failed with "Must use import to load ES Module" — add transform: { "^.+\\.mjs$": "babel-jest" } to the Jest config.

  • #311 add --tests on Jest now adds transform: { "^.+\\.mjs$": "babel-jest" } to the Jest config, and doctor warns when it is missing. d3-shape 3.2.1 ships only .mjs, which jest-expo's transform does not compile, so every suite reaching chart failed with "Must use import to load ES Module".

0.5.1

GitHub release

Patch changes

  • #253 update merges a component's AGENTS.md that a project edited after copying it with an older CLI, instead of skipping it with no base to merge from — so docs still naming bun test pick up the package-manager-neutral text. The MCP add_components tool and the README describe --tests as copying tests written for the project's own runner.

0.5.0

GitHub release

Minor changes

  • #244 add --tests copies each component's bun test suites beside it, records them in the lock, and asks for @types/bun; update keeps copied tests current and update --tests brings them in. The registry ships each item's tests in a separate tests list, so older CLIs are unaffected.

  • #246 add --tests writes copied suites for the project's own runner — Vitest, Jest or Bun, read from package.json or set with tests.runner in native-components.json — and asks for that runner's packages instead of @types/bun. The shipped suites keep to the API all three share, and the registry build fails on one that does not. add --tests and doctor check that the runner can run them — its packages, a Jest config or a Vitest alias, and tsconfigRaw on Expo's tsconfig — and name the fix.

Patch changes

  • #243 Copied components no longer assume Bun or cite tests that are not copied. add strips maintainer-only notes and test-file rows from each component's AGENTS.md, the registry build fails on any shipped file that names Bun or an unshipped test, and the CLI prints Expo commands for the project's own package manager. The agent skill shows npx with its pnpm, yarn and bun equivalents.

0.4.0

GitHub release

Minor changes

  • #211 Add Breadcrumb — the path to the current screen, links back up each level and the current page last.

    • Breadcrumb.List counts its direct Breadcrumb.Items and puts a separator between every pair, none after the last. A hand-placed Breadcrumb.Separator stands in for the automatic one beside it, and the root's separator replaces the chevron everywhere.
    • Past maxItems the middle folds into an ellipsis, keeping itemsBeforeCollapse and itemsAfterCollapse at each end; onEllipsisPress makes it a button and hands over the hidden indices. overflow="scroll" runs the trail on in a horizontal scroller opened at the current page.
    • Breadcrumb.Link is a Pressable announced as a link with a 44-point target and takes an onPress — no router coupling. Breadcrumb.Page is announced selected; separators are hidden from screen readers; chevrons flip in right-to-left layouts.
    • sm and md sizes, on the caption and body text steps.
    • delacour add breadcrumb copies it into a project.
  • #216 Add CircularText — text set around a circle, optionally turning

    The text sits on a react-native-svg TextPath, laid out once by the platform's shaper and closed exactly with textLength. span is a full ring, an arc or natural spacing; side stands the glyphs outside or inside the circle; repeat="fill" repeats a phrase until the ring closes. spin is a steady loop in degrees per second, driven by a frame-callback integrator so a pause (by press or by state) eases to a halt and resumes without a snap, or a controlled angle from a shared value. centerContent stays upright and independently accessible, the ring reads once with separators stripped, and a loop holds still under reduce motion or isMotionCalm.

  • #182 Add ColorPicker — pick a colour by dragging, typing or tapping a preset.

    • Compound anatomy: ColorPicker.Area (saturation and brightness), .Hue, .Alpha, .Preview, .Input (a hex field), .Swatches / .Swatch, .Content and .Trigger. A bare <ColorPicker /> draws the area, the hue track and the field.
    • Every drag runs on the UI thread through one shared value, so nothing re-renders while a finger moves. onValueChange fires per frame, onValueCommit once a change settles.
    • Accepts hex (3, 4, 6, 8 digits), rgb(), rgba(), hsl() and hsla(); reports in format — hex, rgb or hsl. Greys and black keep their hue, and a controlled echo never drifts it.
    • hasAlpha adds an opacity track whose fade keeps the same RGB end to end. presentation="sheet" puts the surface in a BottomSheet behind a trigger row drawn on Input's box.
    • Joins Field, takes Input's sizes and variants, and is adjustable for screen readers in every axis, announcing on commit.
    • The color-picker subpath imports BottomSheet, so it needs @delacour/react-native-bottom-sheet installed even inline.
    • delacour add color-picker copies it into a project.
  • #206 Add Compare, two layers of one frame with a seam the finger drags across — before and after, one screen in two themes, a redesign against the original. The front layer is revealed by a sliding mask rather than resized, so both halves stay in register; the handle is an adjustable with stepped assistive actions, snap points on release, haptics at stops and bounds, horizontal or vertical orientation, and a grab mode that leaves vertical scrolling to the list

  • #190 Add Direction, a per-subtree LTR/RTL wrapper with useDirection and useDirectionSign.

    • <Direction dir="rtl"> mirrors its subtree on the next frame — rows reverse, start/end insets swap — with no restart and no remount, and nests: an inner <Direction dir="ltr"> holds a phone number's order inside an RTL card.
    • Uniwind's rtl: and ltr: variants resolve against the nearest Direction, not the app.
    • useDirection() and useDirectionSign() report the direction to gesture and translateX maths. Outside any Direction they fall back to I18nManager.isRTL rather than throwing.
    • Worklet-safe helpers — resolveDirection, directionSign, physicalSide, mirrorX, logicalDelta — for code that runs on the UI thread.
    • delacour add direction copies it into a project.
  • #222 Add ImageViewer: a fullscreen gallery that opens from a thumbnail, with pinch, pan and double-tap zoom, swipe between images and drag-to-dismiss, plus the media / media-foreground colour tokens.

  • #208 Add MessageScroller, a conversation list anchored at the newest message

    It follows new messages while the reader is at the end and leaves them alone once they have scrolled up, with a floating "jump to latest" button carrying an unread count. Older history loads when the reader reaches the top, guarded to one request per page, and lands without moving the viewport. The list rises with the keyboard, draws day separators and an unread divider, and passes each message's place in its sender's run to renderMessage. useMessageScrollerState drives the same behaviour on another list, such as Screen.ChatList.

  • #218 Add Message, a chat bubble with incoming and outgoing sides, run-aware logical corners, an avatar gutter, author and timestamp, delivery receipts with retry, reactions, actions and a typing indicator — read by a screen reader as one composed label

  • #205 Add Pagination, for moving through the pages of a result set. The numbers variant draws a fixed-length run of page numbers and gaps, so the number under the reader's thumb never slides away as the window moves; a gap never hides a single page, and tapping one jumps pageJump pages. compact draws one "3 / 12" readout that VoiceOver swipes a page at a time, and simple the two labelled arrows alone. Pagination.Summary and Pagination.Status ("41–60 of 472") sit at the leading edge. A controlled page outside [1, count] is drawn clamped and reported once through onPageChange. sm targets reach 44 points with hit slop. delacour add pagination copies it in.

  • #200 Add Post, a social card in four shapes — feed, vote, compact and media — with an author, a tokenised body with tags, mentions and links, a fixed-shape media frame with a scrim, stats whose glyph fills and whose count rolls, and a score pill; all counts and votes stay caller state

  • #195 Add Questionnaire — a guided form, one question at a time. Six question kinds (single and multiple choice with a free-text "Other", text, rating, a numbered scale and yes/no), conditional questions that drop their stale answers, Back that follows the questions actually visited, deliberate skips, a dimmed-not-disabled Next that explains a blocked answer, async submit, controlled answers and navigation, and bar, stepper or count progress. The flow's rules are exported as pure functions, so a server can re-validate a submission.

  • #223 Add SectionProgress — a floating pill over a long reading screen: a ring filling with scroll progress beside the title of the section being read, opening a card that lists every section.

    • Reads useScrollSections: scroll drives the ring and the reveal, value takes activeId, onValueChange takes scrollTo. The ring runs on the UI thread, so scrolling renders nothing; a jump eases over 180ms.
    • Six placements against the safe area, an offset, and a revealAt that latches once crossed. The panel grows out of the pill, scrolls past six rows, and closes on an outside tap, the escape gesture or Android back.
    • SectionProgress.Item takes a color for its marker and for the ring while it is active. progress overrides the scroll; isOpen / onOpenChange control the panel; haptic plays on a section change.
    • VoiceOver reads "Sections, Pricing, 42 percent"; opening moves focus to the active section. Reduce motion turns the panel's scale into a fade.
    • delacour add section-progress copies it into a project, with use-scroll-sections.
  • #214 Add SectionRail — a thin rail of bars along a screen edge, one per section of a long screen, that opens into a labelled outline.

    • SectionRail.Trigger holds one SectionRail.Bar per section and SectionRail.Content one SectionRail.Item per row; level shortens a bar and indents its row by the same step. The active bar's colour eases in, and the active row is drawn in the foreground.
    • Tap the rail to open the outline, or scrub: drag along it and the row under the finger highlights — mapped on the UI thread, with an optional haptic tick per section crossed — and releasing jumps there. A pick lingers for closeDelay before closing; an outside tap or Android's back closes it.
    • placement (end, start) and align (center, bottom) are logical and mirror in right-to-left; the rail clears the safe area by offset. It fills its container and passes every other touch through, so the content under it keeps scrolling.
    • Pairs with useScrollSections: value={sections.activeId} and onValueChange={sections.scrollTo}. value={undefined} stays controlled, so a hook's empty first value does not flip modes.
    • One adjustable element to VoiceOver, named "Sections" and valued by the active title; swiping steps through the sections and focus lands on the active row when the outline opens.
    • delacour add section-rail copies it into a project, along with use-scroll-sections. The registry builder now reads a registryDependencies list from ITEM_META for edges no import records.
  • #221 Add Sortable — a vertical list the user reorders by dragging.

    • Rows move by transform on the UI thread, so a drag renders nothing; onReorder(order, { id, from, to }) fires once, after the row lands, and the transforms clear in the commit that renders the new order.
    • activation="handle" drags from Sortable.Handle; "long-press" lifts the whole row after longPressDelay. A row changes slot when its edge crosses the next row's midpoint, at any heights.
    • isPinned rows never move and the others reorder around them; isDisabled rows cannot be lifted.
    • scrollRef auto-scrolls the enclosing scroller near its edges. haptic plays one feedback on lift and one per slot passed.
    • Screen readers get Move up / Move down actions and "Position 2 of 5", with the new position announced.
    • reorderItems and useSortableItem are exported. delacour add sortable copies it into a project.
  • #179 Add SplitView, two stacked panes with a drag row that settles on named snap points.

  • #177 Add Splitter, resizable panes divided by draggable, adjustable handles, sized in percent.

  • #198 Add TagInput — free-form tags typed into one field box, each a removable chip

    Return or a delimiter commits the text as a chip and keeps the keyboard up; a paste holding delimiters lands as several tags. Duplicates, a max and a validate hook refuse tags through onReject with a reason, and allowOverflow accepts past max while marking the field invalid. Backspace on an empty field arms the last chip, and a second removes it. The box is Input's, it joins an enclosing Field, and tags and draft are each controlled or uncontrolled.

  • #220 Add TextAnimation — five ways a piece of text or a number arrives, on one clock.

    The root reveals a string by word, character or line with a fade, slide or scale, staggered forward, in reverse, from the centre or in a seeded random order. TextAnimation.Typing types a phrase or cycles several with a bar or block caret, TextAnimation.Rotating swaps one word in place inside a sentence, TextAnimation.Counting tweens a formatted number on the UI thread, and TextAnimation.Rolling rolls an odometer whose wheels take the short way round. Nothing reflows while it plays, a screen reader hears the final text once, and under reduce motion every part shows its final text on the first frame.

  • #212 Add Tour — a guided walkthrough: the screen dims, a hole opens around one control, and a card beside it explains it.

    • Wrap each control in a Tour.Step with an order and a title; the tour sorts the steps, measures the current one in window coordinates and re-measures on rotation. onStepChange fires first, so a target can be scrolled into view.
    • The hole is an SVG cutout, rect or circle, that glides between targets on the UI thread and jumps under reduce motion. shape, padding, radius and placement are set on the tour and overridden per step.
    • The card sits below the hole when it fits and above otherwise, centred and clamped to the safe area. Next, Back, Skip and Done, a "2 of 5" progress, and every label replaceable.
    • A React Native Modal by default; isInteractive draws inside the tour's root and passes touches inside the hole through to the control.
    • Open with defaultOpen, the isOpen triple or useTour().start(). onFinish and onSkip are where an app records that the tour was seen.
    • Focus moves to the card on each step; VoiceOver's escape and Android back skip when dismissible.
    • delacour add tour copies it into a project.
  • #207 Add Tree — a hierarchy opened one level at a time, with branches that expand and collapse and optional single or multiple selection. Depth comes from context, closed groups unmount, and hasChildren marks a lazy branch before its children load.

  • #175 Add Combobox — a long or remote list behind a field-shaped trigger, filtered as you type in a bottom sheet.

    • The sheet opens with its search field focused once it has settled. The default filter matches the label and keywords case- and accent-insensitively; filter={{ kind: "custom", match }} takes a predicate and { kind: "none" } shows the options as given, for a server. The matched run of each label is bold, through accents.
    • Single selection by default, where a pick closes the sheet; selectionMode="multiple" keeps it open and shows removable chips on the trigger (folding into +N) and above the search field, with a two-step Backspace remove. value and onValueChange are typed per mode; maxSelected disables the rest once reached.
    • The query is controlled separately with inputValue and onInputValueChange, which reports type, select, clear or close. A picked option stays on the trigger after a server's results move on. allowCustomValue offers the typed text as a value.
    • Built from Select's leaves — the same trigger box and option rows — and joins Field the same way. isClearable, isReadOnly, isLoading, Combobox.Empty and a replaceable Combobox.Trigger cover the rest; the result count is announced to screen readers.
    • Needs @delacour/react-native-bottom-sheet and BottomSheetProvider. delacour add combobox copies it into a project, along with select, chip and bottom-sheet.
  • #172 Add DatePicker, a field-shaped trigger that shows a date (mode="single"), a range ("range") or several ("multiple") and opens a Calendar in a bottom sheet to change it. The sheet closes when the pick is finished — on a day, on a range's second end, or on Done for several — and a half-picked range is held inside the sheet rather than reported. The trigger is the Input box at every size and variant; its text is formatted from the value through the calendar's Intl formatters, with format and formatRange overrides and a long-form screen-reader value. minDate/maxDate/disabledDates, locale and week start pass to the calendar; isOpen/onOpenChange and the value work controlled or uncontrolled; Clear, read-only, presentation="inline", and isInvalid/isDisabled/label/label-tap from a Field. Needs BottomSheetProvider. bunx delacour add date-picker copies it.

  • #199 Add DateTimePicker, one field for a date and a time of day picked together: an Input-shaped trigger that opens a Calendar and TimePicker's wheel in one bottom sheet with one Done, or draws them in place with presentation="inline". The value is a whole { date, time } or null, never half — a day picked first takes the time from the clock, rounded up onto minuteStep; a time picked first lands on today or the nearest allowed day — and every pick and settle reports it, so Done only closes and announces. layout="tabs" (default) puts Date and Time behind tabs and moves to Time once after the first date pick; layout="stacked" draws the calendar over a time row that opens the wheel. minDateTime/maxDateTime are one window that bounds the calendar to its days and the wheel to its times on those days only, re-clamping the time when the date moves; plus disabledDates, hourCycle, showSeconds, locale, a custom format, isClearable, controlled or uncontrolled value and open state, and the Field cascade. The subpath exports the pure DateTimeValue helpers — toNativeDate/fromNativeDate, formatDateTime, serialiseDateTime/parseDateTime, clampDateTime, timeBoundsForDate and more. Needs BottomSheetProvider. bunx delacour add date-time-picker copies it.

  • #186 Add Input.Group.Button, Input.Group.Separator and Input.Group.Block, decorative decorators, and Textarea inside a group

    • Input.Group.Prefix / .Suffix take isDecorative, which hides them from screen readers and lets touches through to the field. It defaults to true when every child is bare text or an Icon.
    • Input.Group.Button is a Button sized one step below the box (square when icon-only), disabled with the group without fading twice, and warns in development when icon-only with no label.
    • Input.Group.Separator draws a hairline whose direction comes from where it is written: vertical in the row, horizontal between a block and the row.
    • Input.Group.Block adds a full-width section above (position="start") or below ("end") the field inside the same border and focus ring. The group always renders an inner row, so adding a block never remounts the field.
    • Textarea reads the group's context and drops its own box, so a message composer is one box.
  • #191 Add MarkdownEditor, a Markdown field with a formatting toolbar, a monospaced write pane and a rendered preview

    The toolbar wraps a selection or inserts markers at the caret, toggles a format off when pressed again, applies headings, quotes and lists to every touched line, and lights from the caret. Return continues a list. The preview draws the toolbar's subset on the Text presets and opens a link only for http, https, mailto and tel. It joins Field like Input, comes in a bar or pill toolbar, and ships applyMarkdownCommand, continueMarkdownList and parseMarkdown as plain functions. The CLI adds it with delacour add markdown-editor.

  • #188 Add Marker, a centred inline note for steps and day breaks between conversation turns

    It comes as a bare default row, a hairline border capsule, or a separator label ruled to both edges. With no handler it is a plain view read as text; give it onPress or onLongPress and it becomes a pressable announced as a button. Marker.Content shimmer breathes the text on the UI thread while a step runs and announces the marker busy, stilled under reduce motion. bunx delacour add marker copies it in.

  • #184 Add NumberInput — a number between a minus and a plus

    Tap a stepper to move by step, hold it to repeat after 400ms and accelerate, or type: while focused the field takes half-typed text ("", "-", "1.") and commits on blur or submit, so it never fights the person typing. Every path goes through one reconcile — snapped to the step, held inside [min, max], rounded to the step's precision — and onValueChange fires only on a committed change. The box is Input.Group's, so it takes Input's variants and sizes and an enclosing Field's label, invalid and disabled state. formatOptions formats the value at rest through Intl; commitMode="validate" keeps an out-of-range number and flags it instead of clamping. The field is adjustable with increment and decrement actions for VoiceOver and TalkBack. The parse, format and step maths are exported as pure helpers. bunx delacour add number-input installs it.

  • #215 Add OtpInput, a one-time code or PIN drawn as a row of cells over one hidden TextInput, so the number pad, a paste, iOS's QuickType code from Messages and Android's SMS autofill all fill every cell at once. Every change is sanitised to pattern (digits, alphanumeric, alphabetic or a one-character RegExp) and cut to length; onComplete fires once per transition to a full code with a success haptic. groupSize draws separators between groups, or write the anatomy out with OtpInput.Group, OtpInput.Slot and OtpInput.Separator. isMasked draws dots without switching off autofill, the caret holds still under reduce motion, the row is one accessible element, and isInvalid / isDisabled cascade from a Field. bunx delacour add otp-input copies it.

  • #194 Add ScrollBlur — a progressive blur at a scrollable's edges that keeps floating controls legible over any background, falling back to a gradient wash with no engine or with Reduce Transparency on. Ships with ScrollBlurLayerProvider, the expo/blur-layer engine (ExpoBlurLayer, behind the new optional peer expo-blur), useReduceTransparency and withAlpha in lib/color. The CLI gains scroll-blur, blur-layer and use-reduce-transparency registry items, and classifies expo-blur as an expo install package.

  • #210 Add ScrollText, text that resolves unit by unit as the screen scrolls past it. Four effects — colour, fade, rise and highlight — over a word, character (by grapheme) or line split, staggered by overlap inside a viewport window. Progress comes from the enclosing Screen, from any scroll view's offset (read against its own bounds with viewport), or from a shared value already in 0..1. Every frame runs on the UI thread and writes only opacity, transform and colour; un-revealed text never drops below a legible floor, reduce motion and isMotionCalm show it fully revealed, and the block reads as one accessibility element with the whole string. delacour add scroll-text copies it in.

  • #197 Add SearchBar, the query field at the top of a list, on Input.Group with no chrome of its own. A clear button that keeps focus and the keyboard, a Cancel action shown never, on focus or always that empties the text and dismisses the keyboard, debounce with onDebouncedChange (flushed by the search key, immediate on clear), isLoading swapping clear for a spinner, and tokens drawn as removable chips with Backspace removing the last. pill and rounded shapes over Input's variants and sizes, and it joins an enclosing Field. delacour add search-bar copies it.

  • #171 Add Select — a closed set of choices behind a field-shaped trigger, picked from a bottom sheet.

    • Single selection by default, where a pick closes the sheet; selectionMode="multiple" keeps it open, draws a box on each row and closes from a Done footer. value and onValueChange are typed per mode — a string or null, or an array — and maxSelected disables the remaining rows once reached.
    • Options come as data through options, or as Select.Item and Select.Group children, and both are drawn from one normalised, virtualised list. isSearchable adds a search field that matches case- and accent-insensitively, hides groups it empties and clears on close.
    • The trigger is Input's own box at every variant and size. Inside a Field the select takes its invalid and disabled state, reads Field.Label as its accessible name and sheet title, and opens when the label is tapped. isReadOnly, isLoading, renderValue and a replaceable Select.Trigger cover the rest.
    • Rows are radios in single mode and checkboxes in multiple to a screen reader; group headings are headers. SelectOptionRow is exported as the leaf a later picker reuses.
    • Needs @delacour/react-native-bottom-sheet and BottomSheetProvider. delacour add select copies it into a project, along with bottom-sheet.
  • #187 Add Signature, a pad to sign with a finger or a stylus. Strokes are drawn on the UI thread and smoothed into ink, a tap leaves a dot, and React renders once per stroke rather than per move. Signature.Undo, .Redo and .Clear disable themselves when they would do nothing, and onRequestAlternative adds a "Can't sign? Type your name" link for anyone who cannot draw. The value is plain JSON that re-fits a pad of another size; the ref adds toSVG() and toPaths(). Four sizes, two variants, read-only and disabled states, and isInvalid / isDisabled cascade from a Field. bunx delacour add signature copies it.

  • #185 Add ThemeSelector, a System, Light or Dark choice read from the live Uniwind theme and applied to it. System is selected while the app follows the device, so a theme changed anywhere else re-selects it. segmented joins the options into one attached run; cards stands them apart as tiles. applyTheme={false} with value turns it into a presentation layer so the app can persist the preference itself — there is no storage dependency — and restoreThemeMode validates a stored string before it reaches Uniwind.setTheme. useThemeSelection reads and sets the mode with no selector mounted. delacour add theme-selector copies it, with toggle-button, icon and field as registry dependencies.

  • #193 Add TimePicker, a time of day on snapping wheels — hour, minute, optional second and, on a 12-hour clock, AM/PM. It presents as an Input-shaped trigger that opens a BottomSheet, or inline with presentation="inline". The value is always a 24-hour TimeValue; hourCycle and locale change only how it reads. It takes minuteStep, minTime/maxTime (out-of-bounds rows dim and a wheel resting on one moves back), showSeconds, visibleRows, a custom format, clearable, controlled or uncontrolled value and open state, and joins an enclosing Field. Parts are TimePicker.Trigger, .Value, .Icon, .Content, .Wheel, .Done and .Clear; TimePickerWheel renders the bare wheel on its own, and the subpath exports the pure time helpers (parseTime, serialiseTime, formatTime, snapTime, clampTime and more). Each wheel column is one adjustable element to screen readers. The subpath imports the sheet engine, so it needs @delacour/react-native-bottom-sheet installed. The CLI can now add time-picker.

  • #180 Add Timeline, a sequence of dated events down a rail or across a snapping strip

    Timeline lays out an order's history, a project's milestones or a match's key moments: each Timeline.Item is a marker on a rail with a title, a date, a description and optional side columns. One zero-based value says how far along the sequence is — events before it completed, at it current, after it upcoming — and the rail fills off that value alone, so it is never half drawn between two events. tone says what kind an event is (info, success, warning, destructive) and tints its marker whatever its progress. Five variants (dot, icon, numbered, card, compact) and two sizes. Horizontally it is a swipeable strip of columns that can snap, fires onColumnChange and a selection haptic as each column reaches the reading edge, and collapses an empty column to a tick. Timeline.List virtualises a long history over a FlatList and supplies each item's position itself. Timeline.Masthead and Timeline.Stats head the sequence. Each event is one VoiceOver stop, read as "Event 2 of 6, completed, success".

    bunx delacour add timeline copies it into your project.

Patch changes

  • #219 Add QRCode: a string a camera can read, drawn as one react-native-svg path, with an optional QRCode.Logo in the middle

    The encoder is in-house and dependency-free — numeric, alphanumeric and UTF-8 byte modes, versions 1–40, levels L/M/Q/H — and is exported as encodeQr and useQRCode for custom renderers. Three module styles (square, rounded, dots) keep the finders concentric; modules snap to whole device pixels; a logo forces level H and grows the version until every block stays recoverable. The tile is dark-on-light in both themes, and an inverted or low-contrast pair warns in development. The registry now also skips *.test-helpers.ts and *.fixtures.ts alongside tests.

  • #183 Add AnimatedBadge, a status pill that rolls its content and springs its width when the status changes.

  • #192 Add Attachment — a file, image, video or recording with a thumbnail, size, upload state and remove

    AttachmentMedia is discriminated by kind, so an image without a source is a type error. Three layouts — chip for a composer, tile for a photo tray, card for inside a message — at three sizes. Upload stays the app's: status is idle, uploading (determinate or looping), error with the app's message, or done, and onRemove / onRetry compose their controls in beside the press target so a remove tap never opens the attachment. A screen reader meets one element with a composed label, progress in ten-point steps and remove / retry as actions. Attachment.Group is a horizontal tray that fades attachments in and out and slides the rest along, stilled under calm motion. delacour add attachment installs it.

  • #174 Add Countdown, the time left until a fixed moment, read from the wall clock on every tick

    It reads the clock rather than decrementing a counter, so it is right after the app was backgrounded or the device slept, and it re-reads on returning to the foreground. Seconds round up — the final second shows 0:01 and onComplete fires once at zero, re-arming when to moves into the future. units picks which units show, the largest absorbing everything above it; trim drops leading zero units. variant is segmented — boxed digits that slide per changed digit, with captions from labels — or inline, one tabular run. urgentBelow turns the digits destructive. The timer label is coarse and names seconds only in the final minute. bunx delacour add countdown copies it into a project.

  • #226 Move to Expo SDK 58: React Native 0.88, React 19.3, Gesture Handler 3

    The peer floors are now react-native >=0.88 and react-native-gesture-handler >=3.3. React Native 0.88 types its components through the strict API by default, so every ref is typed as the host instance it receives — ViewInstance, TextInputInstance, ScrollViewInstance — rather than as the component, and ScreenScrollViewRef is now ScrollViewInstance. Those types do not exist before 0.88. The sheet's and the drawer's pans keep the builder API under Gesture Handler 3's LegacyPanGesture type. The CLI's verify:expo scaffolds against SDK 58.

  • #170 Add FlipCard — two faces of one card and a turn between them

    FlipCard.Front sits in flow and sizes the card, FlipCard.Back fills it, and FlipCard.Trigger turns it under every trigger. trigger is a discriminated union: "press" (a tap anywhere), "swipe" (a drag that scrubs the turn, rubber-bands past either rest and settles on the nearer face or the way it was flicked; swipeDistance exists only here) and "manual" (only face, the ref's flip() / flipTo() or a trigger). The face is state and the rotation follows it: the hidden face leaves the accessibility tree and stops taking touches the moment a turn is committed, which face is drawn is decided by the angle rather than backfaceVisibility, the settled face is announced, and a gesture card always offers an activate action. Under reduce motion or isMotionCalm the faces crossfade instead of turning. delacour add flip-card installs it.

  • #189 Add GridItem and GridItem.Group, a bento tile grid with whole-cell spans

    GridItem.Group measures its width, sizes a cell from columns, gap and aspect or rowHeight, and places each direct child first-fit in writing order from its colSpan and rowSpan, on pixel-snapped edges and the logical start inset. It reserves its height before the first measure, so nothing jumps, and glides tiles to new cells on a relayout unless reduce motion is on. GridItem takes four variants and two sizes, renders a Pressable only when it has a press handler, and announces itself as one stop labelled from its title, value and description. Parts: Background, Media, Title, Value, Description, Footer and Actions; useGridItem() and useGridItemGroup() expose the tile's box and the group's cell size, and bunx delacour add grid-item copies it into a project.

  • #176 Add Leaderboard: ranked people or teams with standard competition ties, the top three on a podium of growing bars or lifted cards, a movement column, and the highlighted entry pinned below the limit cut with its true rank.

    Its podium and rows read entries in any order and rank them stably, order="asc" for times; unit is spoken after every value. bunx delacour add leaderboard copies it into a project.

  • #213 Add ScrollFade, a gradient at a scrollable's edges while content runs past them

    @delacour/react-native-ui/scroll-fade overlays any scrollable — a card row, a list in a Card, a sheet body — and fades its start and end edges only while there is content past them, on either axis. It reads a ScrollSource from useScrollSource, or needs no wiring at all inside a Screen. The fade is a colour, so name the surface with color; edges, size, fadeInDistance and insetStart / insetEnd tune it. The CLI registry gains the scroll-fade item.

  • #181 Add Table, rows and columns of short values lined up by one set of column rules

    columns on the table declares each column once — { flex } or { width }, with an align — and every row's Nth cell reads the Nth rule, so a header, the body and a footer line up without a width on any cell. Bare text is set in tabular figures. Table.Head takes sort to become a button that draws and announces its direction while the caller owns the order; Table.Row takes onPress and isSelected to become one selectable button named by its cells, and each cell announces its column's heading. size, variant="outline" and striped set density and chrome; minWidth scrolls a wide table sideways with edge fades that dissolve into the surface behind it; rows render from a FlatList with index and isLast; Table.Empty keeps the header; Table.Frame puts the table in a card under a title and an action. bunx delacour add table copies it into a project.

0.3.0

GitHub release

Minor changes

  • #126 Add Carousel: swipeable slides with peek, loop, vertical, coverflow, autoplay, dots and arrows. Adds lib/paging, the shared worklet-safe paging maths.

  • #164 Add useScrollSections — section-aware scroll tracking for long screens.

    • useScrollSections({ readingLine }) returns a scrollHandler for an Animated.ScrollView, a register(id) that hands back each section's onLayout, the activeId (React state that changes only when the reader crosses into another section), progress and scrollY as shared values, and scrollTo(id).
    • The active section is resolved on the UI thread and crosses to React only on a change, so scrolling never re-renders the screen.
    • The pure resolvers behind it — resolveActiveSection, resolveScrollProgress and resolveSectionScrollTarget — are worklet-safe and exported from lib/scroll-sections.
    • delacour add use-scroll-sections copies the hook and its resolvers into a project.

Patch changes

  • #163 Add the scroll family's shared base: useScrollSource, scroll-maths and scroll-edges

    @delacour/react-native-ui/hooks/use-scroll-source gives any scrollable a ScrollSource — its offset, content size and layout size as shared values, plus its axis — and the props to spread onto it; ScrollSourceProvider shares one and useResolvedScrollSource is how an effect finds it. lib/scroll-maths holds the worklet maths an edge affordance needs (scrollEdgeOpacity, scrollProgress, readScrollEvent) and lib/scroll-edges the band geometry. A Screen now publishes its own body as a scroll source, so scroll-linked effects inside one need no wiring. The CLI registry gains the three items, and screen now depends on use-scroll-source.

0.2.0

GitHub release

Minor changes

  • #159 Add Map — a themed map for places and paths, at @delacour/react-native-ui/map

    Markers that are React views and anchor on their pin's tip, labels, themed popups, routes, arcs and floating zoom, compass, recenter and locate controls, on MapLibre (@maplibre/maplibre-react-native). The basemap is OpenStreetMap vector tiles from OpenFreeMap — free, no key — painted from the theme's tokens on both platforms, so light, dark and any retuned theme carry through to the map; mapStyle or buildMapStyle swap in other tiles. Props set the opening view; a ref moves the camera with flyTo, fitBounds and recenter. @maplibre/maplibre-react-native is a new optional peer, resolved only by an app that imports /map; delacour add map copies the component into a project and expo installs it.

  • #144 Add delacour update, which brings copied components up to the registry and keeps your edits.

    add now records where each file came from in native-components.lock.json — the registry ref and a hash of the text as the registry served it. update uses that to tell an upstream fix from a local change: a file only the registry moved is replaced, a file only you moved is left alone, and a file both moved is three-way merged. Where both changed the same lines the file is written with git conflict markers and the command exits 1; an edit is never overwritten.

    Your own formatter (Biome or Prettier, from your node_modules) is run over the registry's side before anything is compared, so a file you only reformatted counts as untouched and an update arrives in your style.

    • update --dry-run reports the plan and writes nothing; --json prints it.
    • update --base <ref> merges files that were copied before the lock existed.
    • update --prune deletes untouched files a component no longer has.
    • diff now prints the plan update would apply, so for a tracked file it shows the registry's change on its own rather than both sides.
    • The MCP server gains check_updates and update_components, and doctor warns when components are present with no lock file.

    Commit native-components.lock.json. A project without one keeps working: the first update records every file that still matches the registry.

  • #131 cn() now runs on cn instead of clsx + tailwind-merge: the same output (held to tailwind-merge's on every variant class), much faster on repeated render calls. clsx is no longer a dependency; tailwind-merge stays, as tailwind-variants' peer for tv(). Components added with delacour add now install cn.

Patch changes

  • #148 delacour update now asks what to do with the files it skipped.

    A file copied before native-components.lock.json existed, which no longer matches the registry, has nothing to merge from, so update leaves it alone. Run at a terminal, it now finishes, reports, and then asks about those files: leave them, merge them from a ref you type in (what --base <ref> does), or replace them with the registry's copy. Replacing asks again before touching a file with uncommitted changes.

    Nothing is asked under --yes, --json, in CI or over MCP — there the files stay skipped, as before.

0.1.0

GitHub release

Minor changes

  • #97 Add Meter — a measurement on a fixed scale, coloured by where it falls.

    • Built on Progress: Meter.Header, Meter.Label, Meter.Output, Meter.Track and Meter.Fill are the progress bar's parts, with a root that clamps the reading and judges it. A bare <Meter value={68} /> draws the scale on its own.
    • Judge a reading by low, high and optimum — good, worse and worst regions painted success, warning and destructive, with optimum saying which end is good — or by a thresholds list naming the colour from points along the scale. The prop type allows one or the other, never both.
    • segments draws the scale as whole blocks that fade on and off; any reading above the floor lights at least one.
    • valueLabel takes a function handed the judged reading and its region, so the judgement is put into words that are drawn and spoken alike rather than left to colour. One accessible element with the progressbar role, spoken the way Progress speaks its value.
    • delacour add meter copies it into a project, along with progress.
  • #88 Add Progress — a bar showing how far a task has got, or a looping segment while it is under way.

    • Compound anatomy: Progress.Header, Progress.Label, Progress.Output, Progress.Track and Progress.Fill; a bare <Progress value={40} /> draws the track and fill on its own.
    • The fill animates on the UI thread as a clipped translateX, with no layout pass per frame. isIndeterminate sweeps a segment across the track, and breathes in place instead when Reduce Motion is on.
    • Six colours and three sizes, shared with Slider. formatOptions, a function child on Progress.Output, or valueLabel word the readout.
    • One accessible element with the progressbar role: the value is spoken as a percentage over 0–100 and as a count (18 of 24) over any other range, and an indeterminate bar reports busy with no value.
    • delacour add progress copies it into a project.
  • #137 Add SelectionMode — pick several things at once, then act on them from a bar. A long press enters the mode with the pressed item picked; taps then toggle. SelectionMode.Item wraps any row, avatar or swatch with a round leading mark, a ring or nothing; .Header counts the picks and offers select-all and the way out; .Bar and .Action act on the selection; .Group lays items out as a stacked card, a grid or a horizontal strip. Selection is a set of ids, controllable alongside the mode, with an optional max.

    delacour add selection-mode copies it into a project.

  • #135 Add SlideButton, a control confirmed by dragging a handle across a rail

    The handle tracks the finger exactly and only the release is sprung. A release confirms past threshold (0.9 by default) with a small look-ahead, but a flick from halfway never does, and threshold={1} demands the far end. Controlled, the handle waits at the end until isCompleted answers; a rejected onComplete promise takes it home. Variants secondary, destructive and success; sizes match Button. Screen readers confirm with one named action, and the control flips under RTL.

  • #132 Add StackCard — a deck taken one card at a time by throwing the top one off.

    • Compound anatomy: StackCard.Card, StackCard.Stamp, StackCard.Empty, StackCard.Actions and StackCard.Action. The root sorts its children by type, and anything it does not recognise is a card.
    • One shared value — the top card's offset — drives the drag, the tilt, the stamps and the cards behind, so nothing re-renders during a drag and the next card is already in place when the top one leaves.
    • directions picks which ways a drag throws; a horizontal-only deck lets vertical scrolls through. A flick throws from a short drag, and a disallowed direction gives a little and returns.
    • layout draws the cards behind as a stack, a fan or flat, depth of them. Only a window around the top card is mounted, so a deck of five hundred costs what a deck of five does.
    • Undo brings the last card back from the side it left, and a controlled deck that leaves index where it was declines the throw and the card flies back.
    • Each allowed direction, and undo, is a screen-reader action on the top card, named by directionLabels; the cards behind are hidden. Under Reduce Motion a card fades rather than flies.
    • delacour add stack-card copies it into a project.
  • #92 Add Avatar

    A person as a picture. The initials of name are painted first and the image goes on top, so the circle is never empty while a picture loads or after it fails, and a failed source retries on its own when its URI or headers change. variant and color paint the fallback on the six colours Badge takes; sm, md, lg and xl are fixed edges.

    Avatar.Badge pins a count or, left empty, a presence dot to a corner without the circle clipping it. Avatar.Group overlaps its faces with the first on top, rings each in the page background, and counts the people past max — or up to total — in a trailing +N tile that onOverflowPress can make a button.

    delacour add avatar copies it in.

  • #124 Add Calendar, an always-visible month grid for picking one day (mode="single"), several ("multiple") or a range ("range"), controlled or uncontrolled. Always six weeks, so the height never moves; swipe or tap the arrows to page, and tap the caption for month and year jump views. minDate/maxDate and disabled matchers (a date, a span, weekdays or a predicate) limit what can be picked; names and the first day of the week come from the locale. Two variants, three sizes on the input scale, optional Surface chrome, custom day content, and isInvalid / isDisabled / the label cascade from a Field. Days are plain CalendarDates — { year, month, day }, no time zone — with the helpers to compare, add, format, parse and serialise them, so no date library is needed. bunx delacour add calendar copies it.

  • #117 Add isMotionCalm to DelacourProvider, and useCalmMotion()

    An E2E build passes isMotionCalm and every decorative loop holds still, exactly as it does under the OS reduce-motion setting — so a test runner that waits for the screen to settle before each gesture (Argent, Detox, Maestro) never waits out a shimmering Skeleton. Motion that is the behaviour keeps moving: Spinner and an indeterminate Progress are untouched.

    useCalmMotion() (@delacour/react-native-ui/hooks/use-calm-motion) is the one question a component asks before it loops — reduce motion, or the app asking — and CalmMotionProvider sets the app's half for a hand-composed root. Skeleton and Skeleton.Group now read it.

  • #90 Add Chip, an interactive pill for filters, tags and removable tokens

    What a chip does follows from its props. With no handler it is a plain tag; with onPress it is a button; with isSelected, defaultSelected or onSelectedChange it is a filter that toggles — controlled or uncontrolled, with a selection haptic, announced to a screen reader as selected or not. onClose adds a remove control with a press of its own, so removing a chip never also toggles it.

    import { Chip } from "@delacour/react-native-ui/chip";
    
    <Chip isSelected={open} onSelectedChange={setOpen} variant="outline">
      <Icon icon={IconFilter1} />
      <Chip.Label>Open only</Chip.Label>
    </Chip>;

    It shares Badge's colours, sizes and tones — an unselected soft or outline chip is painted exactly as the badge beside it — and selection swaps in a solid fill in the chip's colour without changing its size, so a wrapping row never reflows. Parts: Chip.Label, Chip.StartContent, Chip.EndContent, Chip.CloseButton, and useChip() for a child that restyles itself on selection.

    bunx delacour add chip copies it in, along with badge, whose tones it reads.

    Pressable now multiplies a className's opacity into its press feedback instead of overwriting it, so a disabled Button, Badge or Chip fades as its variant says it should.

  • #87 Add Collapsible — one section of content shown and hidden by its own trigger. Controlled (isOpen / onOpenChange) or uncontrolled (defaultOpen), with Collapsible.Trigger, .Title, .Description, .Indicator and .Content. The panel's height is measured and sprung, so whatever sits below follows it; the panel mounts on first open and stays mounted, and a closed panel leaves the accessibility tree. Shares Accordion's variants, sizes and motion, and bunx delacour add collapsible copies it without pulling in an accordion.

  • #136 Add Fab, one primary action floating over the screen it belongs to. placement pins it to the bottom start, centre or end of its nearest positioned ancestor, clear of the safe area, or leaves it in flow when omitted; sizes are 44, 56 and 64pt from new --spacing-fab-* tokens, in primary, secondary, surface and destructive. isExtended with Fab.Label makes a labelled stadium. Fab.Group unfolds a dial of Fab.Actions over a scrim from one spring with a stagger, turns its plus into a cross, unmounts the actions once closed, closes on the scrim, an action or Android back, and takes isOpen / defaultOpen / onOpenChange. delacour add fab copies it in.

  • #93 Add Item, a row of media, text and actions for lists and settings. It renders as a Pressable when given onPress and as a plain view otherwise, has default, outline and muted surfaces, sm/md/lg sizes, a vertical orientation for cards, isDisabled and isSelected states, and Item.Media (bare icon, icon tile or clipped image), Item.Content, Item.Title, Item.Description, Item.Actions, Item.Header, Item.Footer, Item.Group and Item.Separator. Dropped into a ListGroup it takes the group's size, draws no surface of its own and lines up with the group's dividers. The CLI can now add item.

  • #85 Add Label, a form control's name with required, invalid and disabled states

    Label renders Text.Label and takes a colour, never a scale, so it keeps the type scale the rest of a form uses. isRequired appends a destructive asterisk as a nested run behind a no-break space, so a label that wraps carries the mark after its last word, and announces the label as "Email, required" rather than reading the asterisk aloud. isInvalid turns it destructive and isDisabled fades it and reports it disabled to assistive technology.

    import { Label } from "@delacour/react-native-ui/label";
    
    <Label isRequired>Email</Label>;

    bunx delacour add label copies it in, bringing text and tv with it.

  • #138 Add ProgressButton, a button held rather than tapped to confirm. A fill grows from the leading edge over holdDuration (2000 ms by default) and onComplete fires only when it reaches the end, read from the animation's own finish rather than a timer. Released early, the fill plays back at the same rate, and a second press resumes from where it is. Every variant (primary, secondary, destructive, success) rests on the same surface and carries its colour in a label drawn twice, so the text stays readable across the wipe. It takes the button's sizes and a pill or rounded shape, draws a tick on completion or a caller's ProgressButton.Done, rewinds by travelling back with isAutoReset or a controlled isCompleted, steps in fifths under reduced motion, and completes on a screen reader's activate. delacour add progress-button copies it in.

  • #91 Add Rating, a row of stars that reads or sets a score. Tap a star or drag along the row to set it, in whole stars or any step that divides one (step={0.5} for halves); a read-only rating draws any value, filling a fraction of a star. allowClear lets a tap on the value held withdraw it, Rating.Output shows the value, and the row is one adjustable control to screen readers. Six colours, three sizes, and isInvalid / isDisabled cascade from a Field. bunx delacour add rating copies it.

  • #96 Add Skeleton, a placeholder for content that is still loading

    It comes in three shapes — rect, line and circle — and either shimmers a glint across itself or pulses its opacity, on the UI thread. Pass the real content as children and the placeholder takes its exact size, then fades the content in when isLoading flips off with nothing moving. Skeleton.Lines draws a paragraph, and Skeleton.Group keeps every skeleton inside it on one clock and one loading flag. Motion stops under the OS reduce-motion setting, and a placeholder stays hidden from screen readers unless it has a label. bunx delacour add skeleton copies it in.

  • #99 Add Steps, a stepper for multi-step flows

    Steps shows where someone is in a flow: numbered indicators joined by a line, each step completed, current or upcoming against one zero-based value. It runs horizontally, with titles under the indicators, or vertically, with titles and descriptions beside them, in two variants and three sizes. The connectors are drawn for you — the root counts its Steps.Items and every one but the last draws a line to the next, filled once the value has passed it.

    A step can be loading (a spinner in the indicator), invalid (a cross and a destructive title) or disabled, and completed overrides whether it reads as done. Steps take presses and move the value there; isLinear keeps the steps ahead closed, and a controlled value with no onValueChange is a read-only progress display whose steps are not announced as buttons. Steps.Panel holds a step's form or summary below its title, outside the tap target, so its controls stay reachable by a screen reader. Each step is announced as its title with a value such as "Step 2 of 3, completed".

    bunx delacour add steps copies it into your project.

  • #89 Add Textarea, a multiline text field sized in rows. It renders Input's box — variants, sizes, focus, invalid, disabled and the Field cascade — and adds a height derived from rows and the size's line height, autoGrow up to maxRows, and a showCount character count against maxLength that turns destructive at the limit. delacour add textarea copies it, with input and text as registry dependencies.

  • #84 Add ToggleButton, a button that stays pressed. On its own it is controlled with isSelected and onSelected or holds its own state from defaultSelected; inside ToggleButton.Group the group owns one array of selected values, in multiple or single selection mode, with isSelectionRequired to keep the last choice from being cleared. Each state draws with one of the button's own variants (default, outline, ghost), so sizes, icons, loading and press feedback are the button's. An attached group joins into one run like Button.Group; a detached one wraps with a gap. The state is announced as a toggle button that is checked, or as a selected radio in a single-choice group. delacour add toggle-button copies it in.

  • #35 init copies the root provider in and doctor accepts DelacourProvider as the gesture root

    delacour init now adds the provider item alongside styles, so DelacourProvider is in the ui directory before anything is mounted, and its follow-up list names that import rather than a bare GestureHandlerRootView. doctor's Gesture Handler check passes on either name, reads a blank template's root App.tsx as well as app/ and src/, and no longer counts the copied provider.tsx itself as the app mounting it.

  • #49 add sets the project up itself

    add no longer fails on a project with no native-components.json. It runs init first — writing the config, wrapping Metro, pointing Tailwind at the components and copying the theme and the root provider in — and then adds what was asked for. That was previously a prompt, and only where there was a terminal to ask in, so --yes, CI and every MCP call hit MissingConfigError while a human sailed past. --no-init is the opt-out, and -s, --src <dir> forwards to init so a template that keeps its files at the project root still needs one command.

    init now returns the result of the add it ends on, so a caller that set a project up still learns what the components need from npm.

    New MCP tool init_project, for the layout an agent cannot infer — a shared package in a monorepo, or a source directory that is not src. add_components needs it no more often than a person needs init.

  • #25 One theme file, the same shape everywhere

    theme.css is now the one file in styles/ that is yours, and its header says so. What delacour init ships, what the docs site's /theme page emits with no preset, and what delacour theme writes are held to the same shape, declaration for declaration.

    delacour theme converts theme.css in place. Paste a shadcn or tweakcn globals.css over the file and run the command with no argument: a file already in Uniwind's shape is left alone, a shadcn-shaped one is rewritten, and anything else is named. delacour doctor fails on a theme.css still in shadcn's :root / .dark shape, which Uniwind reads as a utility class named dark and a dark theme that never arrives.

    The converter fills what shadcn v4 stopped declaring. --destructive-foreground — which Button, Badge, Switch, Slider and Checkbox all paint with — and the shadow scale are now derived when a source omits them, and the light --elevated derivation follows the card, as the shipped file already did.

    The shipped defaults are shadcn's current ones. The chart ramp is the neutral greys shadcn init writes today, and the default typeface is each platform's own sans rather than Geist, which a fresh app had never loaded. The /theme page shows theme.css first, with shadcn's globals.css in a second tab for a web app sharing the theme.

  • #51 delacour skills installs an agent skill

    An agent asked for "a button" writes plausible JSX — the right shape, and none of the parts that matter: the icon that inherits its size from the button's context, the spinner that replaces the icon so the label does not shift, the expo install route for the native modules underneath.

    bunx delacour@alpha skills installs a skill into whichever assistants a project uses — Claude Code, Cursor, OpenCode or Codex, detected from the directories present, or named with --agent, in --scope project or user. The files are bundled into the binary, so a run works offline and installs the skill matching the version you are pinned to. The docs site serves the same bytes at /skills/delacour-ui/SKILL.md.

    The skill names no component on purpose. A catalogue on someone's disk is stale the day the next component ships, and silently so — it teaches list, view, add and doctor instead, and spends its own words on the failures that produce no error message.

Patch changes

  • #117 Fix Accordion and Collapsible panels that never opened on their first expand in Release builds

    The item's effect decided "has the panel measured?" by reading the measured height's shared value on the JS thread, straight after the panel's onLayout had written it. That write is queued onto the UI runtime and the read does not drain the queue, so a Release build read the unmeasured sentinel every time, bailed, and nothing re-ran it — the trigger said expanded and the panel stayed shut. Debug builds were slow enough to hide it. "Measured" is now React state, the decision is a pure accordionTravelTarget / collapsibleTravelTarget, and a test sweeps both components for any JS-thread read of the height.

  • #94 Add Alert, a status message on a Surface

    Five statuses — default, info, success, warning and destructive — pick the leading glyph and colour it and the title from one token, while the description stays muted. variant="soft" washes the surface in the status's soft fill; variant="surface" keeps a neutral fill that steps off whatever it sits in. Three sizes move padding, type and glyph together. Composed from Alert.Indicator, Alert.Content, Alert.Title, Alert.Description, Alert.Action and Alert.CloseButton; isDismissible adds the close control, uncontrolled or through isOpen / onOpenChange, and useAlert().dismiss closes it from an action. bunx delacour add alert copies it into a project, with surface alongside.

  • #95 Add Card, a content surface with a header, a body and a footer

    Built on Surface, so it takes the same four fills and steps to the next one when nested. Six parts — Card.Header, Card.Title, Card.Description, Card.Action, Card.Content and Card.Footer — share one inset through context across three sizes, and the padding lives on the parts, so media placed straight in the card reaches its edges. Card.Action is pinned to the header's corner wherever it is written, the title follows the foreground token of the card's fill, and Card.Footer variant="band" sets the footer into the card on the next fill down. useCard() exposes the size and fill to custom parts, and bunx delacour add card copies it into a project, with surface alongside.

  • #116 add bottom-sheet installs the engine instead of the library it replaced: @delacour/react-native-bottom-sheet from npm, and react-native-teleport through expo install, because it ships a Fabric portal view and needs a rebuild. @gorhom/bottom-sheet has left the install map, doctor watches react-native-teleport for a duplicated copy in a monorepo, and its New Architecture failure now names BottomSheet, whose portal does not render on the old one.

  • #106 Install Delacour packages from the CLI's own release line. An alpha CLI (delacour@alpha) adds @delacour/react-native-ui@alpha and @delacour/react-native-charts@alpha, and a stable CLI adds them untagged from latest. A peer on a Delacour package is still written as >=0.0.0-0, so either line satisfies it.

  • #125 Add Dialog, a centred card over a dimmed app that asks for a decision or a short input

    @delacour/react-native-ui/dialog, and delacour add dialog. A compound root with Trigger, Content, Close, Header, Title, Description, Body and Footer, drawn through the overlay foundation, so it sits above every bottom sheet and the navigator's header. isOpen / defaultOpen / onOpenChange, and isDismissible={false} for an alert dialog that only its own actions close. Four sizes (sm, md, lg, full), a plain or panel footer that stacks on sm, a card that lifts just clear of the keyboard, a fade-only entrance under Reduce Motion, and accessibility focus that moves to the title on open and back to the trigger on close.

    Also: Overlay.Portal's missing-provider warning no longer names the npm package, so a copied overlay carries no reference to it.

  • #129 Add Drawer, a panel that slides in from an edge and covers the app until dismissed

    @delacour/react-native-ui/drawer, and delacour add drawer. A compound root with Trigger, Content, Header, Title, Description, Body, Footer and Close, drawn through the overlay foundation, so it sits above every bottom sheet and the navigator's header. Four sides — start and end swap under RTL, plus top and bottom — and four sizes as a capped fraction of the window. Swipe toward the edge to dismiss: past 40% or on a fling it carries on from the finger, short of that it eases back, a selection haptic marks the threshold and the scrim thins with the drag. The panel pads the safe-area insets on its screen sides, fades instead of sliding under Reduce Motion, and moves accessibility focus to the title on open and back to the trigger on close.

  • #86 Add EmptyState

    A placeholder for a list or screen with nothing in it. EmptyState.Header stacks Media, Title and Description, and EmptyState.Content holds the actions. variant="card" draws a dashed block for an empty section beside populated ones, and size scales the padding, media and type together. A bare Icon in Media inherits its size and colour, and Media variant="icon" tints a box behind it.

    delacour add empty-state copies it into a project.

  • #130 Add Feedback, a dialog for writing — the field in a recessed well, the actions on the band around it

    @delacour/react-native-ui/feedback, and delacour add feedback. Built on Dialog, so open state, portal, scrim, back, escape, focus and the keyboard lift are Dialog's. A compound root with Trigger, Content, Panel, Title, Close, Field, Footer, Action, Cancel and Submit, and useFeedback(). The draft is value / defaultValue / onValueChange on the root and survives close and reopen until clear(). Submit is gated by canSubmitFeedback (empty and whitespace are empty, canSubmitEmpty for chips or a rating), passes the trimmed text, shows its loading state and makes the field read-only while a returned promise is in flight, and never closes on its own. The well eases to its measured height when a multi-step flow swaps its content, so the card never jumps; under Reduce Motion it changes height at once. The field grows from minRows (6) to maxRows (12) before it scrolls, and takes the title's text as its label.

  • #98 Add Kpi, one number, its change and a sparkline of how it got there

    Built on Card, so it takes the card's four fills and three sizes and sits on the card's inset. Kpi.Trend takes a signed percentage and colours it by what it means rather than by its sign — goodDirection on the card says whether up, down or neither is good news, and every trend inside follows it — as a line of text or a badge with an arrow, announced as one string. Kpi.Sparkline is a Chart line with no axes in the card's series colour, under the number or in a fixed column beside it, and holding it scrubs a point: the KPI keeps the scrubbed index as controllable state that useKpi() reads. isLoading holds placeholders of each part's size, and Kpi.Group lays several metrics out in a row or a column, optionally on one surface with rules between. Chart gains frameClassName, which sizes the plot's frame. bunx delacour add kpi copies it into a project, with card and chart alongside.

  • #120 Add OverlayProvider, the layer every overlay is drawn on

    @delacour/react-native-ui/overlay mounts the one teleport host that overlays and bottom sheets share, and a registry that orders them in bands — every overlay above every sheet, an anchored panel above the dialog that opened it, a toast above both — and gives Android's back button to the topmost overlay only. Overlay.Portal, Overlay.Scrim, useOverlayPresence (keeps an overlay mounted through its exit animation) and useOverlayBackHandler are exported for building more. BottomSheetProvider now detects an OverlayProvider above or below it and mounts teleport's provider only once; the engine's provider takes hasPortalProvider to make that possible. bunx delacour add overlay copies it into a project.

  • #127 Add Popover — a small panel anchored to its trigger that flips and shifts to stay inside the safe area and above the keyboard, with an arrow that keeps pointing at the trigger, a title, a description, a close control, an optional scrim, width="trigger" and a scrollable body. Its anchoring pieces — resolveAnchoredPosition, useAnchorMeasure, useAnchoredContent and AnchoredArrow — are exported for other anchored overlays. delacour add popover copies it.

  • #83 Add Surface, a rounded container on the theme's fill ladder

    Four fills — default, secondary, tertiary and transparent — and four padding steps. A surface that names no variant steps to the next fill from the one it sits in, so a panel inside a card never vanishes into it, and a transparent surface passes the plane beneath it through. useSurface() exposes the resolved fill to custom children, and padding="none" clips, for content bled to the corners. bunx delacour add surface copies it into a project.

  • #122 Add Toast — a brief message shown with toast() from anywhere, including outside React, and drawn by one <ToastViewport /> mounted inside OverlayProvider. Toasts stack at the top or bottom edge, three drawn with the newest in front and the rest queued; they sit above the home indicator and the keyboard, pause their clock while touched or backgrounded, and swipe away toward their edge or sideways. Statuses, glyphs and title colours are Alert's; toast.promise turns one loading toast into the success or the failure in place; render composes a custom card from <Toast>'s parts. Each toast is announced as it appears and stays at least ten seconds under a screen reader. Alert's status glyphs move to the alert-glyphs.ts leaf so the two share them. delacour add toast copies it.

  • #128 Add Tooltip — a short label anchored to its control that opens on a long press (or a press, with openOn="press"), hides itself after duration, and closes on any outside tap while letting that tap through to what it landed on. It flips and shifts like Popover, has an inverted and a surface variant, an arrow that points at the trigger, and reads its label to a screen reader on the trigger without opening. OverlayProvider now hears every touch that starts beneath it, through subscribeTouchStart, without claiming any. delacour add tooltip copies it.

  • #48 Publish the libraries under the @delacour org

    delacour-react-native-ui is now @delacour/react-native-ui, and delacour-react-native-charts is now @delacour/react-native-charts. The old names are deprecated and take no further versions. (Both were briefly spelled @delacour/native-ui and @delacour/charts in this repository; neither reached npm, so the line above is the whole story for anyone installing.) Nothing about the components changed — swap the package and the import prefix:

    bun remove delacour-react-native-ui delacour-react-native-charts
    bun add @delacour/react-native-ui@alpha @delacour/react-native-charts@alpha
    - import { Button } from "delacour-react-native-ui/button";
    + import { Button } from "@delacour/react-native-ui/button";

    and in global.css, @import '@delacour/react-native-ui/styles';.

    The CLI keeps its name. delacour add chart now installs @delacour/react-native-charts.

  • #22 Add bar, scatter, candlestick and pie charts, stacked areas and horizontal bars

    @delacour/react-native-charts gains four marks and a second root. ChartBar draws one bar per datum on a cubic-cornered rect path, so a corner radius animates without snapping; sibling bars share a step and bars naming one stackId stack in data space, so the y domain covers the running totals rather than the tallest series. ChartArea takes the same stackId. ChartScatter is one Skia path per series, and ChartCandlestick draws every candle through every sentiment path so a colour flip is a morph rather than a cut. orientation="horizontal" swaps the axis roles at the model, so bars grow rightward from a category axis. PolarChart is the new root, with PieSlices on a fixed-verb path that morphs between any two data sets and a scrub-free tap that resolves a slice index. @delacour/react-native-charts/core exports the bar, scatter, candle and slice geometry alongside the scales.

    @delacour/react-native-ui/chart skins all of it. Chart.Bar, Chart.Scatter and Chart.Candlestick join Chart.Line and Chart.Area; bars group by being siblings, stack by sharing a stackId, round their value end from --radius, and take labels. Candles borrow success, destructive and muted-foreground for their sentiment. Over bars or candles Chart.Tooltip.X becomes a band one step wide. PieChart is a second root — PieChart.Slice, .Label, .Center, .Tooltip and .Legend — whose categories are its rows, with innerRadius for a donut and a tap-driven readout.

    Series colours now dedupe before the theme lookup, so twenty slices walking the five-token ramp resolve five tokens rather than throwing past the eighth.

    The CLI's chart registry item picks up the new files and names the new marks in its description.

  • #52 Teach one verb, and hold the docs to the CLI

    The Quick start forks before the first command — a new app, an existing one, or hand it to an agent — and lands all three on one rendered Button. It scaffolds Expo's with-router-uniwind example and never names init: add does that setup itself, so a reader learns one verb.

    The landing page's hero says add too, the package path names Uniwind as a real setup step with links to its own guide, and a handful of claims that had gone stale are corrected — the registry holds no copy of the library's source, /llms.txt lives on ui.delacour.co.nz, and the releases page no longer reports a version that was never published.

    docs-commands.test.ts walks the commander program and holds every copyable delacour … in the repository to it — verb, long flags, positional arity, and whether a component named in an example is in the registry. It caught add --src before a reader did.

  • #72 List every native peer in the install command. The charts README's expo install line named only Skia, and delacour add chart skipped Gesture Handler — the scrub and the pie's tap then never fire, with no error.

  • #104 Point the "registry may be newer than this CLI" hint at --ref develop. main now moves only on a release, so unreleased components live on develop.

  • #50 doctor stops failing correct Metro configs, and tells you a path you can paste

    • withUniwindConfig is not the outermost wrapper, when it was. The check required the export expression to begin with withUniwindConfig, so it failed both const c = withUniwindConfig(…); module.exports = c; and the reassignment a Metro config uses when it applies several wrappers in turn — config = withUniwindConfig(config, …). A wrapper genuinely applied after Uniwind still fails, which is the point of the check.
    • cssEntryFile does not point at the configured entry restated the problem rather than the fix. It now says which file Metro actually compiles and why that is the one that has to win.
    • The CSS import it told you to add did not resolve. It was ./ plus the file's basename, which is right only when the root layout sits in the same directory as the CSS. On the ordinary Expo Router layout it is ../styles/global.css, and that is what both doctor and init now print — from one function.
  • #50 Adopt the Tailwind entry an app already has, and print the root layout

    One entry, never two. init picked <src>/styles/global.css unless a wrapped Metro config named another, so an app that had installed Uniwind and written its own global.css — without wiring Metro to it yet — got a second entry. Tailwind then compiled the file holding the @source globs while the app imported the one without them: every component unstyled, nothing logged, and doctor reporting that nothing imports the entry. An entry already on disk is now adopted.

    The root layout is printed whole. Two of the three follow-ups are edits to one file, so init shows that file rather than describing it — and detects Expo Router, whose root layout is a different file with a different shape (app/_layout.tsx rendering a <Slot />, not App.tsx). The bullet and the snippet take their import paths from one function, so they cannot disagree.

  • #55 Refresh the lockfile and drop an obsolete patch

    bun.lock pinned expo-modules-jsi@57.0.5 while a dependency already required ~57.1.0, so bun install --frozen-lockfile failed the moment anything forced a re-resolve. The patch that pinning existed for — declaring retainRuntimeScheduler / releaseRuntimeScheduler for Swift bridging — ships in 57.1.0 itself, so it and its patchedDependencies entry are gone.

  • #49 Stop the last line suggesting a command that already ran

    add delegates to init whenever a project has no config, so a run that copied button in ended on "Ready. delacour add button to get started." — the command the reader had just run. The outro now names what landed instead: "Ready. button is yours to edit."

    It no longer mentions doctor either. The follow-up block directly above it already ends on delacour doctor, and two consecutive lines pointing at the same command read as a glitch rather than as emphasis.

  • #22 Add charts: a headless Skia engine, and the Chart component that skins it

    @delacour/react-native-charts is new — a token-free charting engine for React Native, drawn with Skia, animated with Reanimated and driven by Gesture Handler. It ships CartesianChart with Line, Area, Grid and both axes, a scrub whose dot rides the drawn curve rather than hopping between data points, and path morphing that never falls back to snapping. @delacour/react-native-charts/core is every scale, tick, curve and solver in it, importable with no Skia in the module graph.

    @delacour/react-native-ui/chart is that engine wearing the theme. A shadcn-shaped config names each series and assigns --chart-1 … --chart-5 by position, so a call site writes <Chart.Line yKey="revenue" /> and never a colour. Parts are placed rather than configured: Chart.Grid, Chart.Line, Chart.Area, Chart.XAxis and Chart.YAxis draw into the canvas, while Chart.Tooltip and Chart.Legend are React Native views layered over and under it.

    Also new: --spacing-chart-sm/md/lg, because a canvas has no intrinsic height and a dashboard's rows only line up if every chart agrees on one.

    This needs a dev-client rebuild. @shopify/react-native-skia is a native module and is new to the workspace — run expo prebuild --clean and rebuild before running the playground.

    The CLI learns two things: how to install a Skia-backed component, and that @delacour/react-native-charts publishes to the alpha tag while this repository is in pre mode, since a bare bun add of it would resolve latest and find nothing.

  • #50 Work correctly on a project that already has Uniwind

    Three bugs, all found against Expo's with-router-uniwind example — a scaffold that arrives with Uniwind, Tailwind and Metro already wired.

    • init wrote its @source block into a file Metro does not compile. A wired Metro config names its own cssEntryFile, and init recorded <src>/styles/global.css regardless — so Tailwind scanned a file with no globs in it, every class the components use was dropped, and every component rendered unstyled with nothing logged. It now reads cssEntryFile and dtsFile off the wrapped config and records those.
    • doctor failed the official template. Its outermost-wrapper check required the export expression to begin with withUniwindConfig, and that template assigns the wrapped config to a variable before exporting it. The exported name is now followed to the last thing assigned to it above the export, a few hops deep.
    • init asked for a CSS import that was already there. The follow-up list is now built from what the project actually has, so neither the CSS import nor the provider is listed once it is mounted.
  • #50 Refuse to stack a second Tailwind transform

    Nothing noticed when a project already had NativeWind. It is Tailwind for React Native too: it compiles className, and it does that by wrapping Metro. Wrapping Metro again on top of it leaves classes resolving through whichever wrapper ran last and a build that fails naming neither library.

    init now warns before it touches metro.config.js, and doctor carries a Styling check that keeps saying so.

    It is deliberately not fixed automatically. Which of the two a project keeps is the owner's call, and uninstalling someone's styling library is not a thing a component CLI gets to do — so the message points at Uniwind's migration guide instead.

0.1.0-alpha.6

Patch changes

  • #104 Point the "registry may be newer than this CLI" hint at --ref develop. main now moves only on a release, so unreleased components live on develop.

0.1.0-alpha.5

Patch changes

  • #72 List every native peer in the install command. The charts README's expo install line named only Skia, and delacour add chart skipped Gesture Handler — the scrub and the pie's tap then never fire, with no error.

0.1.0-alpha.4

Minor changes

  • #49 add sets the project up itself

    add no longer fails on a project with no native-components.json. It runs init first — writing the config, wrapping Metro, pointing Tailwind at the components and copying the theme and the root provider in — and then adds what was asked for. That was previously a prompt, and only where there was a terminal to ask in, so --yes, CI and every MCP call hit MissingConfigError while a human sailed past. --no-init is the opt-out, and -s, --src <dir> forwards to init so a template that keeps its files at the project root still needs one command.

    init now returns the result of the add it ends on, so a caller that set a project up still learns what the components need from npm.

    New MCP tool init_project, for the layout an agent cannot infer — a shared package in a monorepo, or a source directory that is not src. add_components needs it no more often than a person needs init.

  • #51 delacour skills installs an agent skill

    An agent asked for "a button" writes plausible JSX — the right shape, and none of the parts that matter: the icon that inherits its size from the button's context, the spinner that replaces the icon so the label does not shift, the expo install route for the native modules underneath.

    bunx delacour@alpha skills installs a skill into whichever assistants a project uses — Claude Code, Cursor, OpenCode or Codex, detected from the directories present, or named with --agent, in --scope project or user. The files are bundled into the binary, so a run works offline and installs the skill matching the version you are pinned to. The docs site serves the same bytes at /skills/delacour-ui/SKILL.md.

    The skill names no component on purpose. A catalogue on someone's disk is stale the day the next component ships, and silently so — it teaches list, view, add and doctor instead, and spends its own words on the failures that produce no error message.

Patch changes

  • #52 Teach one verb, and hold the docs to the CLI

    The Quick start forks before the first command — a new app, an existing one, or hand it to an agent — and lands all three on one rendered Button. It scaffolds Expo's with-router-uniwind example and never names init: add does that setup itself, so a reader learns one verb.

    The landing page's hero says add too, the package path names Uniwind as a real setup step with links to its own guide, and a handful of claims that had gone stale are corrected — the registry holds no copy of the library's source, /llms.txt lives on ui.delacour.co.nz, and the releases page no longer reports a version that was never published.

    docs-commands.test.ts walks the commander program and holds every copyable delacour … in the repository to it — verb, long flags, positional arity, and whether a component named in an example is in the registry. It caught add --src before a reader did.

  • #50 doctor stops failing correct Metro configs, and tells you a path you can paste

    • withUniwindConfig is not the outermost wrapper, when it was. The check required the export expression to begin with withUniwindConfig, so it failed both const c = withUniwindConfig(…); module.exports = c; and the reassignment a Metro config uses when it applies several wrappers in turn — config = withUniwindConfig(config, …). A wrapper genuinely applied after Uniwind still fails, which is the point of the check.
    • cssEntryFile does not point at the configured entry restated the problem rather than the fix. It now says which file Metro actually compiles and why that is the one that has to win.
    • The CSS import it told you to add did not resolve. It was ./ plus the file's basename, which is right only when the root layout sits in the same directory as the CSS. On the ordinary Expo Router layout it is ../styles/global.css, and that is what both doctor and init now print — from one function.
  • #50 Adopt the Tailwind entry an app already has, and print the root layout

    One entry, never two. init picked <src>/styles/global.css unless a wrapped Metro config named another, so an app that had installed Uniwind and written its own global.css — without wiring Metro to it yet — got a second entry. Tailwind then compiled the file holding the @source globs while the app imported the one without them: every component unstyled, nothing logged, and doctor reporting that nothing imports the entry. An entry already on disk is now adopted.

    The root layout is printed whole. Two of the three follow-ups are edits to one file, so init shows that file rather than describing it — and detects Expo Router, whose root layout is a different file with a different shape (app/_layout.tsx rendering a <Slot />, not App.tsx). The bullet and the snippet take their import paths from one function, so they cannot disagree.

  • #55 Refresh the lockfile and drop an obsolete patch

    bun.lock pinned expo-modules-jsi@57.0.5 while a dependency already required ~57.1.0, so bun install --frozen-lockfile failed the moment anything forced a re-resolve. The patch that pinning existed for — declaring retainRuntimeScheduler / releaseRuntimeScheduler for Swift bridging — ships in 57.1.0 itself, so it and its patchedDependencies entry are gone.

  • #49 Stop the last line suggesting a command that already ran

    add delegates to init whenever a project has no config, so a run that copied button in ended on "Ready. delacour add button to get started." — the command the reader had just run. The outro now names what landed instead: "Ready. button is yours to edit."

    It no longer mentions doctor either. The follow-up block directly above it already ends on delacour doctor, and two consecutive lines pointing at the same command read as a glitch rather than as emphasis.

  • #50 Work correctly on a project that already has Uniwind

    Three bugs, all found against Expo's with-router-uniwind example — a scaffold that arrives with Uniwind, Tailwind and Metro already wired.

    • init wrote its @source block into a file Metro does not compile. A wired Metro config names its own cssEntryFile, and init recorded <src>/styles/global.css regardless — so Tailwind scanned a file with no globs in it, every class the components use was dropped, and every component rendered unstyled with nothing logged. It now reads cssEntryFile and dtsFile off the wrapped config and records those.
    • doctor failed the official template. Its outermost-wrapper check required the export expression to begin with withUniwindConfig, and that template assigns the wrapped config to a variable before exporting it. The exported name is now followed to the last thing assigned to it above the export, a few hops deep.
    • init asked for a CSS import that was already there. The follow-up list is now built from what the project actually has, so neither the CSS import nor the provider is listed once it is mounted.
  • #50 Refuse to stack a second Tailwind transform

    Nothing noticed when a project already had NativeWind. It is Tailwind for React Native too: it compiles className, and it does that by wrapping Metro. Wrapping Metro again on top of it leaves classes resolving through whichever wrapper ran last and a build that fails naming neither library.

    init now warns before it touches metro.config.js, and doctor carries a Styling check that keeps saying so.

    It is deliberately not fixed automatically. Which of the two a project keeps is the owner's call, and uninstalling someone's styling library is not a thing a component CLI gets to do — so the message points at Uniwind's migration guide instead.

0.1.0-alpha.3

Patch changes

  • #48 Publish the libraries under the @delacour org

    delacour-react-native-ui is now @delacour/react-native-ui, and delacour-react-native-charts is now @delacour/react-native-charts. The old names are deprecated and take no further versions. Nothing about the components changed — swap the package and the import prefix:

    bun remove delacour-react-native-ui delacour-react-native-charts
    bun add @delacour/react-native-ui@alpha @delacour/react-native-charts@alpha
    - import { Button } from "delacour-react-native-ui/button";
    + import { Button } from "@delacour/react-native-ui/button";

    and in global.css, @import '@delacour/react-native-ui/styles';.

    The CLI keeps its name. delacour add chart now installs @delacour/react-native-charts.

0.1.0-alpha.2

Minor changes

  • #35 init copies the root provider in and doctor accepts DelacourProvider as the gesture root

    delacour init now adds the provider item alongside styles, so DelacourProvider is in the ui directory before anything is mounted, and its follow-up list names that import rather than a bare GestureHandlerRootView. doctor's Gesture Handler check passes on either name, reads a blank template's root App.tsx as well as app/ and src/, and no longer counts the copied provider.tsx itself as the app mounting it.

0.1.0-alpha.1

Minor changes

  • #25 One theme file, the same shape everywhere

    theme.css is now the one file in styles/ that is yours, and its header says so. What delacour init ships, what the docs site's /theme page emits with no preset, and what delacour theme writes are held to the same shape, declaration for declaration.

    delacour theme converts theme.css in place. Paste a shadcn or tweakcn globals.css over the file and run the command with no argument: a file already in Uniwind's shape is left alone, a shadcn-shaped one is rewritten, and anything else is named. delacour doctor fails on a theme.css still in shadcn's :root / .dark shape, which Uniwind reads as a utility class named dark and a dark theme that never arrives.

    The converter fills what shadcn v4 stopped declaring. --destructive-foreground — which Button, Badge, Switch, Slider and Checkbox all paint with — and the shadow scale are now derived when a source omits them, and the light --elevated derivation follows the card, as the shipped file already did.

    The shipped defaults are shadcn's current ones. The chart ramp is the neutral greys shadcn init writes today, and the default typeface is each platform's own sans rather than Geist, which a fresh app had never loaded. The /theme page shows theme.css first, with shadcn's globals.css in a second tab for a web app sharing the theme.

Patch changes

  • #22 Add bar, scatter, candlestick and pie charts, stacked areas and horizontal bars

    @delacour/react-native-charts gains four marks and a second root. ChartBar draws one bar per datum on a cubic-cornered rect path, so a corner radius animates without snapping; sibling bars share a step and bars naming one stackId stack in data space, so the y domain covers the running totals rather than the tallest series. ChartArea takes the same stackId. ChartScatter is one Skia path per series, and ChartCandlestick draws every candle through every sentiment path so a colour flip is a morph rather than a cut. orientation="horizontal" swaps the axis roles at the model, so bars grow rightward from a category axis. PolarChart is the new root, with PieSlices on a fixed-verb path that morphs between any two data sets and a scrub-free tap that resolves a slice index. @delacour/react-native-charts/core exports the bar, scatter, candle and slice geometry alongside the scales.

    @delacour/react-native-ui/chart skins all of it. Chart.Bar, Chart.Scatter and Chart.Candlestick join Chart.Line and Chart.Area; bars group by being siblings, stack by sharing a stackId, round their value end from --radius, and take labels. Candles borrow success, destructive and muted-foreground for their sentiment. Over bars or candles Chart.Tooltip.X becomes a band one step wide. PieChart is a second root — PieChart.Slice, .Label, .Center, .Tooltip and .Legend — whose categories are its rows, with innerRadius for a donut and a tap-driven readout.

    Series colours now dedupe before the theme lookup, so twenty slices walking the five-token ramp resolve five tokens rather than throwing past the eighth.

    The CLI's chart registry item picks up the new files and names the new marks in its description.

  • #22 Add charts: a headless Skia engine, and the Chart component that skins it

    @delacour/react-native-charts is new — a token-free charting engine for React Native, drawn with Skia, animated with Reanimated and driven by Gesture Handler. It ships CartesianChart with Line, Area, Grid and both axes, a scrub whose dot rides the drawn curve rather than hopping between data points, and path morphing that never falls back to snapping. @delacour/react-native-charts/core is every scale, tick, curve and solver in it, importable with no Skia in the module graph.

    @delacour/react-native-ui/chart is that engine wearing the theme. A shadcn-shaped config names each series and assigns --chart-1 … --chart-5 by position, so a call site writes <Chart.Line yKey="revenue" /> and never a colour. Parts are placed rather than configured: Chart.Grid, Chart.Line, Chart.Area, Chart.XAxis and Chart.YAxis draw into the canvas, while Chart.Tooltip and Chart.Legend are React Native views layered over and under it.

    Also new: --spacing-chart-sm/md/lg, because a canvas has no intrinsic height and a dashboard's rows only line up if every chart agrees on one.

    This needs a dev-client rebuild. @shopify/react-native-skia is a native module and is new to the workspace — run expo prebuild --clean and rebuild before running the playground.

    The CLI learns two things: how to install a Skia-backed component, and that @delacour/react-native-charts publishes to the alpha tag while this repository is in pre mode, since a bare bun add of it would resolve latest and find nothing.

On this page