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
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 withdelacour telemetry disable,DO_NOT_TRACK=1,DELACOUR_TELEMETRY_DISABLED=1orDELACOUR_CLI_TELEMETRY_DISABLED=1;DELACOUR_TELEMETRY_DEBUG=1prints what would be sent instead. Newdelacour telemetry [status|enable|disable]command, documented at/docs/native/cli#telemetry.
0.7.0
Minor changes
-
#327
native-components.jsonrecords aversion.initwrites the current one, andupdatebrings an older config up to it first: at a terminal it asks once about each option the file predates (tests.enabled, andprefixwhile nothing is copied yet), then records the answers and the version.--yeskeeps the defaults;--json, a pipe, CI and the MCP server leave the file alone and name what is still to choose.doctorwarns on a config that is behind, or written by a newer CLI. The docs gain an Updating guide. -
#327
initasks what components should call the library in place ofDelacourUI(--prefix <name>oninitor the firstaddanswers it), andupdateasks a project that has noprefixeven after components are copied: answering renames it through every copy, merging into edited files, and lists the project's own files that still importDelacourProvider. Aprefixset by hand innative-components.jsonis renamed through on the nextupdatethe same way. -
#327 When
addorupdatecopies tests, it addstest:ui(every copied suite),test:ui:unit(the logic suites) and, on Jest,test:ui:render(the render tests) topackage.json, scoped to the component directories so the project's owntestscript is untouched. A script already under one of those names is kept as it is. -
#327
updatelists 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.addandupdatenow ask which missing packages to install, one checkbox each, instead of all or nothing.updateno 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
updateno 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
addandupdateinstall a package again at the library's range whenpackage.jsonhas it below that range — a project ontailwind-variants3 now gets 4, which the copiedlib/tv.tsimports from. The report names each one and the version it found.updatealso 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_REGISTRYnames the registry to read when neither--registrynorregistry.urlinnative-components.jsondoes — a path, a URL orgithub: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-gradientandexpo-blureach have a mock of their own undertest/mocks/, copied only with a component that installs the module and added to Jest'ssetupFiles— so a project withoutchartno longer fails every suite with "Cannot find module 'react-native-skia'". -
#326
updateno longer crashes withEPIPEwhen the project's formatter exits before reading its input; the file is compared unformatted and the failure reported once.
0.6.0
Minor changes
-
#289 Add
prefixtonative-components.json:addandupdaterename everyDelacourUIdisplay name and thrown-message tag to it, andDelacourProviderto<prefix>Provider—"prefix": "UI"givesUI.Flow.ProviderandUIProvider.initanddoctorname the provider the same way.addnow installs the plain npm packages the components use (tailwind-variants, tailwind-merge, cn, tailwindcss, the icon set) at the ranges@delacour/react-native-uideclares, instead of npm's latest. tailwind-variants 4.0.0 broke every copied*.variants.ts. -
#311 Add component render tests with
@testing-library/react-nativeunder Jest.delacour add --testsnow copies a component's render suite, the Jest setup that stubs its native modules and arenderWithProvidershelper into a Jest project, adds the setup to the project's Jest config, and remembers the choice innative-components.json. Fix a disabledSwitchflipping on a screen reader's activation or itsFieldlabel press. -
#291 Upgrade
tailwind-variantsto v4.lib/tvnow buildstvwithcreateTVfromtailwind-variants/config, the only v4 entry that acceptstwMergeConfig— the default entry throws on it, so atv.tscopied by an earlier CLI breaks as soon as v4 is installed.tailwind-mergeis no longer a dependency: v4 ships its own compiled merge tables.delacour addstops installing it.v4's types drop numeric variant keys, so
KpiVariantProps["colorIndex"],PlannerVariantProps["colorIndex"]andLeaderboardVariantProps["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 --testsprepends a/// <reference types>for the runner's ambient types (bun, ornodeunder Jest and Vitest), since TypeScript 6 — what Expo SDK 58 ships — defaultstypesto[]and every copied suite failedtscin a fresh app. -
#312
doctorwarns when a Jest project usingcharthas no transform for.mjs. d3-shape 3.2.1 ships only.mjs, whichjest-expo's transform does not compile, so every suite reachingchartfailed with "Must use import to load ES Module" — addtransform: { "^.+\\.mjs$": "babel-jest" }to the Jest config. -
#311
add --testson Jest now addstransform: { "^.+\\.mjs$": "babel-jest" }to the Jest config, anddoctorwarns when it is missing. d3-shape 3.2.1 ships only.mjs, whichjest-expo's transform does not compile, so every suite reachingchartfailed with "Must use import to load ES Module".
0.5.1
Patch changes
- #253
updatemerges a component'sAGENTS.mdthat a project edited after copying it with an older CLI, instead of skipping it with no base to merge from — so docs still namingbun testpick up the package-manager-neutral text. The MCPadd_componentstool and the README describe--testsas copying tests written for the project's own runner.
0.5.0
Minor changes
-
#244
add --testscopies each component'sbun testsuites beside it, records them in the lock, and asks for@types/bun;updatekeeps copied tests current andupdate --testsbrings them in. The registry ships each item's tests in a separatetestslist, so older CLIs are unaffected. -
#246
add --testswrites copied suites for the project's own runner — Vitest, Jest or Bun, read frompackage.jsonor set withtests.runnerinnative-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 --testsanddoctorcheck that the runner can run them — its packages, a Jest config or a Vitest alias, andtsconfigRawon Expo's tsconfig — and name the fix.
Patch changes
- #243 Copied components no longer assume Bun or cite tests that are not copied.
addstrips maintainer-only notes and test-file rows from each component'sAGENTS.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 showsnpxwith its pnpm, yarn and bun equivalents.
0.4.0
Minor changes
-
#211 Add
Breadcrumb— the path to the current screen, links back up each level and the current page last.Breadcrumb.Listcounts its directBreadcrumb.Items and puts a separator between every pair, none after the last. A hand-placedBreadcrumb.Separatorstands in for the automatic one beside it, and the root'sseparatorreplaces the chevron everywhere.- Past
maxItemsthe middle folds into an ellipsis, keepingitemsBeforeCollapseanditemsAfterCollapseat each end;onEllipsisPressmakes 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.Linkis aPressableannounced as a link with a 44-point target and takes anonPress— no router coupling.Breadcrumb.Pageis announced selected; separators are hidden from screen readers; chevrons flip in right-to-left layouts.smandmdsizes, on the caption and body text steps.delacour add breadcrumbcopies it into a project.
-
#216 Add
CircularText— text set around a circle, optionally turningThe text sits on a
react-native-svgTextPath, laid out once by the platform's shaper and closed exactly withtextLength.spanis a full ring, an arc or natural spacing;sidestands the glyphs outside or inside the circle;repeat="fill"repeats a phrase until the ring closes.spinis a steadyloopin degrees per second, driven by a frame-callback integrator so apause(by press or by state) eases to a halt and resumes without a snap, or acontrolledangle from a shared value.centerContentstays upright and independently accessible, the ring reads once with separators stripped, and aloopholds still under reduce motion orisMotionCalm. -
#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,.Contentand.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.
onValueChangefires per frame,onValueCommitonce a change settles. - Accepts hex (3, 4, 6, 8 digits),
rgb(),rgba(),hsl()andhsla(); reports informat—hex,rgborhsl. Greys and black keep their hue, and a controlled echo never drifts it. hasAlphaadds an opacity track whose fade keeps the same RGB end to end.presentation="sheet"puts the surface in aBottomSheetbehind a trigger row drawn onInput's box.- Joins
Field, takesInput's sizes and variants, and is adjustable for screen readers in every axis, announcing on commit. - The
color-pickersubpath importsBottomSheet, so it needs@delacour/react-native-bottom-sheetinstalled even inline. delacour add color-pickercopies it into a project.
- Compound anatomy:
-
#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 anadjustablewith stepped assistive actions, snap points on release, haptics at stops and bounds, horizontal or vertical orientation, and agrabmode that leaves vertical scrolling to the list -
#190 Add
Direction, a per-subtree LTR/RTL wrapper withuseDirectionanduseDirectionSign.<Direction dir="rtl">mirrors its subtree on the next frame — rows reverse,start/endinsets 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:andltr:variants resolve against the nearestDirection, not the app. useDirection()anduseDirectionSign()report the direction to gesture andtranslateXmaths. Outside anyDirectionthey fall back toI18nManager.isRTLrather than throwing.- Worklet-safe helpers —
resolveDirection,directionSign,physicalSide,mirrorX,logicalDelta— for code that runs on the UI thread. delacour add directioncopies 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-foregroundcolour tokens. -
#208 Add
MessageScroller, a conversation list anchored at the newest messageIt 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.useMessageScrollerStatedrives the same behaviour on another list, such asScreen.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. Thenumbersvariant 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 jumpspageJumppages.compactdraws one "3 / 12" readout that VoiceOver swipes a page at a time, andsimplethe two labelled arrows alone.Pagination.SummaryandPagination.Status("41–60 of 472") sit at the leading edge. A controlledpageoutside[1, count]is drawn clamped and reported once throughonPageChange.smtargets reach 44 points with hit slop.delacour add paginationcopies 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:scrolldrives the ring and the reveal,valuetakesactiveId,onValueChangetakesscrollTo. 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 arevealAtthat 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.Itemtakes acolorfor its marker and for the ring while it is active.progressoverrides the scroll;isOpen/onOpenChangecontrol the panel;hapticplays 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-progresscopies it into a project, withuse-scroll-sections.
- Reads
-
#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.Triggerholds oneSectionRail.Barper section andSectionRail.ContentoneSectionRail.Itemper row;levelshortens 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
haptictick per section crossed — and releasing jumps there. A pick lingers forcloseDelaybefore closing; an outside tap or Android's back closes it. placement(end,start) andalign(center,bottom) are logical and mirror in right-to-left; the rail clears the safe area byoffset. It fills its container and passes every other touch through, so the content under it keeps scrolling.- Pairs with
useScrollSections:value={sections.activeId}andonValueChange={sections.scrollTo}.value={undefined}stays controlled, so a hook's empty first value does not flip modes. - One
adjustableelement 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-railcopies it into a project, along withuse-scroll-sections. The registry builder now reads aregistryDependencieslist fromITEM_METAfor 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 fromSortable.Handle;"long-press"lifts the whole row afterlongPressDelay. A row changes slot when its edge crosses the next row's midpoint, at any heights.isPinnedrows never move and the others reorder around them;isDisabledrows cannot be lifted.scrollRefauto-scrolls the enclosing scroller near its edges.hapticplays 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.
reorderItemsanduseSortableItemare exported.delacour add sortablecopies it into a project.
- Rows move by transform on the UI thread, so a drag renders nothing;
-
#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 chipReturn or a delimiter commits the text as a chip and keeps the keyboard up; a paste holding delimiters lands as several tags. Duplicates, a
maxand avalidatehook refuse tags throughonRejectwith a reason, andallowOverflowaccepts pastmaxwhile marking the field invalid. Backspace on an empty field arms the last chip, and a second removes it. The box isInput's, it joins an enclosingField, 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.Typingtypes a phrase or cycles several with a bar or block caret,TextAnimation.Rotatingswaps one word in place inside a sentence,TextAnimation.Countingtweens a formatted number on the UI thread, andTextAnimation.Rollingrolls 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.Stepwith anorderand atitle; the tour sorts the steps, measures the current one in window coordinates and re-measures on rotation.onStepChangefires first, so a target can be scrolled into view. - The hole is an SVG cutout,
rectorcircle, that glides between targets on the UI thread and jumps under reduce motion.shape,padding,radiusandplacementare 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
Modalby default;isInteractivedraws inside the tour's root and passes touches inside the hole through to the control. - Open with
defaultOpen, theisOpentriple oruseTour().start().onFinishandonSkipare 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 tourcopies it into a project.
- Wrap each control in a
-
#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, andhasChildrenmarks 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
keywordscase- 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.valueandonValueChangeare typed per mode;maxSelecteddisables the rest once reached. - The query is controlled separately with
inputValueandonInputValueChange, which reportstype,select,clearorclose. A picked option stays on the trigger after a server's results move on.allowCustomValueoffers the typed text as a value. - Built from Select's leaves — the same trigger box and option rows — and joins
Fieldthe same way.isClearable,isReadOnly,isLoading,Combobox.Emptyand a replaceableCombobox.Triggercover the rest; the result count is announced to screen readers. - Needs
@delacour/react-native-bottom-sheetandBottomSheetProvider.delacour add comboboxcopies it into a project, along withselect,chipandbottom-sheet.
- The sheet opens with its search field focused once it has settled. The default filter matches the label and
-
#172 Add
DatePicker, a field-shaped trigger that shows a date (mode="single"), a range ("range") or several ("multiple") and opens aCalendarin 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 theInputbox at everysizeandvariant; its text is formatted from the value through the calendar'sIntlformatters, withformatandformatRangeoverrides and a long-form screen-reader value.minDate/maxDate/disabledDates,localeand week start pass to the calendar;isOpen/onOpenChangeand the value work controlled or uncontrolled; Clear, read-only,presentation="inline", andisInvalid/isDisabled/label/label-tap from aField. NeedsBottomSheetProvider.bunx delacour add date-pickercopies it. -
#199 Add
DateTimePicker, one field for a date and a time of day picked together: anInput-shaped trigger that opens aCalendarandTimePicker's wheel in one bottom sheet with one Done, or draws them in place withpresentation="inline". The value is a whole{ date, time }ornull, never half — a day picked first takes the time from the clock, rounded up ontominuteStep; 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/maxDateTimeare 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; plusdisabledDates,hourCycle,showSeconds,locale, a customformat,isClearable, controlled or uncontrolled value and open state, and theFieldcascade. The subpath exports the pureDateTimeValuehelpers —toNativeDate/fromNativeDate,formatDateTime,serialiseDateTime/parseDateTime,clampDateTime,timeBoundsForDateand more. NeedsBottomSheetProvider.bunx delacour add date-time-pickercopies it. -
#186 Add
Input.Group.Button,Input.Group.SeparatorandInput.Group.Block, decorative decorators, andTextareainside a groupInput.Group.Prefix/.SuffixtakeisDecorative, which hides them from screen readers and lets touches through to the field. It defaults to true when every child is bare text or anIcon.Input.Group.Buttonis aButtonsized 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.Separatordraws a hairline whose direction comes from where it is written: vertical in the row, horizontal between a block and the row.Input.Group.Blockadds 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.Textareareads 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 previewThe 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
Textpresets and opens a link only for http, https, mailto and tel. It joinsFieldlikeInput, comes in abarorpilltoolbar, and shipsapplyMarkdownCommand,continueMarkdownListandparseMarkdownas plain functions. The CLI adds it withdelacour add markdown-editor. -
#188 Add
Marker, a centred inline note for steps and day breaks between conversation turnsIt comes as a bare
defaultrow, a hairlinebordercapsule, or aseparatorlabel ruled to both edges. With no handler it is a plain view read as text; give itonPressoronLongPressand it becomes a pressable announced as a button.Marker.Content shimmerbreathes the text on the UI thread while a step runs and announces the marker busy, stilled under reduce motion.bunx delacour add markercopies it in. -
#184 Add
NumberInput— a number between a minus and a plusTap 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 — andonValueChangefires only on a committed change. The box isInput.Group's, so it takesInput's variants and sizes and an enclosingField's label, invalid and disabled state.formatOptionsformats the value at rest throughIntl;commitMode="validate"keeps an out-of-range number and flags it instead of clamping. The field isadjustablewith increment and decrement actions for VoiceOver and TalkBack. The parse, format and step maths are exported as pure helpers.bunx delacour add number-inputinstalls it. -
#215 Add
OtpInput, a one-time code or PIN drawn as a row of cells over one hiddenTextInput, 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 topattern(digits,alphanumeric,alphabeticor a one-characterRegExp) and cut tolength;onCompletefires once per transition to a full code with asuccesshaptic.groupSizedraws separators between groups, or write the anatomy out withOtpInput.Group,OtpInput.SlotandOtpInput.Separator.isMaskeddraws dots without switching off autofill, the caret holds still under reduce motion, the row is one accessible element, andisInvalid/isDisabledcascade from aField.bunx delacour add otp-inputcopies 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 withScrollBlurLayerProvider, theexpo/blur-layerengine (ExpoBlurLayer, behind the new optional peerexpo-blur),useReduceTransparencyandwithAlphainlib/color. The CLI gainsscroll-blur,blur-layeranduse-reduce-transparencyregistry items, and classifiesexpo-bluras anexpo installpackage. -
#210 Add
ScrollText, text that resolves unit by unit as the screen scrolls past it. Four effects —colour,fade,riseandhighlight— over aword,character(by grapheme) orlinesplit, staggered byoverlapinside a viewportwindow. Progress comes from the enclosingScreen, from any scroll view's offset (read against its own bounds withviewport), 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 andisMotionCalmshow it fully revealed, and the block reads as one accessibility element with the whole string.delacour add scroll-textcopies it in. -
#197 Add
SearchBar, the query field at the top of a list, onInput.Groupwith 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,debouncewithonDebouncedChange(flushed by the search key, immediate on clear),isLoadingswapping clear for a spinner, andtokensdrawn as removable chips with Backspace removing the last.pillandroundedshapes overInput's variants and sizes, and it joins an enclosingField.delacour add search-barcopies 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.valueandonValueChangeare typed per mode — a string ornull, or an array — andmaxSelecteddisables the remaining rows once reached. - Options come as data through
options, or asSelect.ItemandSelect.Groupchildren, and both are drawn from one normalised, virtualised list.isSearchableadds 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 aFieldthe select takes its invalid and disabled state, readsField.Labelas its accessible name and sheet title, and opens when the label is tapped.isReadOnly,isLoading,renderValueand a replaceableSelect.Triggercover the rest. - Rows are radios in single mode and checkboxes in multiple to a screen reader; group headings are headers.
SelectOptionRowis exported as the leaf a later picker reuses. - Needs
@delacour/react-native-bottom-sheetandBottomSheetProvider.delacour add selectcopies it into a project, along withbottom-sheet.
- Single selection by default, where a pick closes the 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,.Redoand.Cleardisable themselves when they would do nothing, andonRequestAlternativeadds 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 addstoSVG()andtoPaths(). Four sizes, two variants, read-only and disabled states, andisInvalid/isDisabledcascade from aField.bunx delacour add signaturecopies 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.segmentedjoins the options into one attached run;cardsstands them apart as tiles.applyTheme={false}withvalueturns it into a presentation layer so the app can persist the preference itself — there is no storage dependency — andrestoreThemeModevalidates a stored string before it reachesUniwind.setTheme.useThemeSelectionreads and sets the mode with no selector mounted.delacour add theme-selectorcopies it, withtoggle-button,iconandfieldas 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 anInput-shaped trigger that opens aBottomSheet, or inline withpresentation="inline". The value is always a 24-hourTimeValue;hourCycleandlocalechange only how it reads. It takesminuteStep,minTime/maxTime(out-of-bounds rows dim and a wheel resting on one moves back),showSeconds,visibleRows, a customformat,clearable, controlled or uncontrolled value and open state, and joins an enclosingField. Parts areTimePicker.Trigger,.Value,.Icon,.Content,.Wheel,.Doneand.Clear;TimePickerWheelrenders the bare wheel on its own, and the subpath exports the pure time helpers (parseTime,serialiseTime,formatTime,snapTime,clampTimeand more). Each wheel column is one adjustable element to screen readers. The subpath imports the sheet engine, so it needs@delacour/react-native-bottom-sheetinstalled. The CLI can nowadd time-picker. -
#180 Add
Timeline, a sequence of dated events down a rail or across a snapping stripTimelinelays out an order's history, a project's milestones or a match's key moments: eachTimeline.Itemis a marker on a rail with a title, a date, a description and optional side columns. One zero-basedvaluesays 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.tonesays 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, firesonColumnChangeand a selection haptic as each column reaches the reading edge, and collapses an empty column to a tick.Timeline.Listvirtualises a long history over aFlatListand supplies each item's position itself.Timeline.MastheadandTimeline.Statshead the sequence. Each event is one VoiceOver stop, read as "Event 2 of 6, completed, success".bunx delacour add timelinecopies it into your project.
Patch changes
-
#219 Add
QRCode: a string a camera can read, drawn as onereact-native-svgpath, with an optionalQRCode.Logoin the middleThe 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
encodeQranduseQRCodefor 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.tsand*.fixtures.tsalongside 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 removeAttachmentMediais discriminated bykind, so an image without asourceis a type error. Three layouts —chipfor a composer,tilefor a photo tray,cardfor inside a message — at three sizes. Upload stays the app's:statusisidle,uploading(determinate or looping),errorwith the app's message, ordone, andonRemove/onRetrycompose 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.Groupis a horizontal tray that fades attachments in and out and slides the rest along, stilled under calm motion.delacour add attachmentinstalls it. -
#174 Add
Countdown, the time left until a fixed moment, read from the wall clock on every tickIt 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:01andonCompletefires once at zero, re-arming whentomoves into the future.unitspicks which units show, the largest absorbing everything above it;trimdrops leading zero units.variantissegmented— boxed digits that slide per changed digit, with captions fromlabels— orinline, one tabular run.urgentBelowturns the digits destructive. Thetimerlabel is coarse and names seconds only in the final minute.bunx delacour add countdowncopies 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.88andreact-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, andScreenScrollViewRefis nowScrollViewInstance. Those types do not exist before 0.88. The sheet's and the drawer's pans keep the builder API under Gesture Handler 3'sLegacyPanGesturetype. The CLI'sverify:exposcaffolds against SDK 58. -
#170 Add
FlipCard— two faces of one card and a turn between themFlipCard.Frontsits in flow and sizes the card,FlipCard.Backfills it, andFlipCard.Triggerturns it under every trigger.triggeris 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;swipeDistanceexists only here) and"manual"(onlyface, the ref'sflip()/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 thanbackfaceVisibility, the settled face is announced, and a gesture card always offers anactivateaction. Under reduce motion orisMotionCalmthe faces crossfade instead of turning.delacour add flip-cardinstalls it. -
#189 Add
GridItemandGridItem.Group, a bento tile grid with whole-cell spansGridItem.Groupmeasures its width, sizes a cell fromcolumns,gapandaspectorrowHeight, and places each direct child first-fit in writing order from itscolSpanandrowSpan, on pixel-snapped edges and the logicalstartinset. 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.GridItemtakes four variants and two sizes, renders aPressableonly 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,FooterandActions;useGridItem()anduseGridItemGroup()expose the tile's box and the group's cell size, andbunx delacour add grid-itemcopies 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 thelimitcut with its true rank.Its podium and rows read entries in any order and rank them stably,
order="asc"for times;unitis spoken after every value.bunx delacour add leaderboardcopies it into a project. -
#213 Add
ScrollFade, a gradient at a scrollable's edges while content runs past them@delacour/react-native-ui/scroll-fadeoverlays any scrollable — a card row, a list in aCard, a sheet body — and fades its start and end edges only while there is content past them, on either axis. It reads aScrollSourcefromuseScrollSource, or needs no wiring at all inside aScreen. The fade is a colour, so name the surface withcolor;edges,size,fadeInDistanceandinsetStart/insetEndtune it. The CLI registry gains thescroll-fadeitem. -
#181 Add
Table, rows and columns of short values lined up by one set of column rulescolumnson the table declares each column once —{ flex }or{ width }, with analign— 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.Headtakessortto become a button that draws and announces its direction while the caller owns the order;Table.RowtakesonPressandisSelectedto become one selectable button named by its cells, and each cell announces its column's heading.size,variant="outline"andstripedset density and chrome;minWidthscrolls a wide table sideways with edge fades that dissolve into the surface behind it; rows render from aFlatListwithindexandisLast;Table.Emptykeeps the header;Table.Frameputs the table in a card under a title and an action.bunx delacour add tablecopies it into a project.
0.3.0
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 ascrollHandlerfor anAnimated.ScrollView, aregister(id)that hands back each section'sonLayout, theactiveId(React state that changes only when the reader crosses into another section),progressandscrollYas shared values, andscrollTo(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,resolveScrollProgressandresolveSectionScrollTarget— are worklet-safe and exported fromlib/scroll-sections. delacour add use-scroll-sectionscopies the hook and its resolvers into a project.
Patch changes
-
#163 Add the scroll family's shared base:
useScrollSource,scroll-mathsandscroll-edges@delacour/react-native-ui/hooks/use-scroll-sourcegives any scrollable aScrollSource— its offset, content size and layout size as shared values, plus its axis — and the props to spread onto it;ScrollSourceProvidershares one anduseResolvedScrollSourceis how an effect finds it.lib/scroll-mathsholds the worklet maths an edge affordance needs (scrollEdgeOpacity,scrollProgress,readScrollEvent) andlib/scroll-edgesthe band geometry. AScreennow publishes its own body as a scroll source, so scroll-linked effects inside one need no wiring. The CLI registry gains the three items, andscreennow depends onuse-scroll-source.
0.2.0
Minor changes
-
#159 Add
Map— a themed map for places and paths, at@delacour/react-native-ui/mapMarkers 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;mapStyleorbuildMapStyleswap in other tiles. Props set the opening view; a ref moves the camera withflyTo,fitBoundsandrecenter.@maplibre/maplibre-react-nativeis a new optional peer, resolved only by an app that imports/map;delacour add mapcopies the component into a project andexpo installs it. -
#144 Add
delacour update, which brings copied components up to the registry and keeps your edits.addnow records where each file came from innative-components.lock.json— the registry ref and a hash of the text as the registry served it.updateuses 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 exits1; 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-runreports the plan and writes nothing;--jsonprints it.update --base <ref>merges files that were copied before the lock existed.update --prunedeletes untouched files a component no longer has.diffnow prints the planupdatewould apply, so for a tracked file it shows the registry's change on its own rather than both sides.- The MCP server gains
check_updatesandupdate_components, anddoctorwarns when components are present with no lock file.
Commit
native-components.lock.json. A project without one keeps working: the firstupdaterecords every file that still matches the registry. -
#131
cn()now runs oncninstead ofclsx+tailwind-merge: the same output (held to tailwind-merge's on every variant class), much faster on repeated render calls.clsxis no longer a dependency;tailwind-mergestays, astailwind-variants' peer fortv(). Components added withdelacour addnow installcn.
Patch changes
-
#148
delacour updatenow asks what to do with the files it skipped.A file copied before
native-components.lock.jsonexisted, which no longer matches the registry, has nothing to merge from, soupdateleaves 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
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.TrackandMeter.Fillare 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,highandoptimum— good, worse and worst regions painted success, warning and destructive, withoptimumsaying which end is good — or by athresholdslist naming the colour from points along the scale. The prop type allows one or the other, never both. segmentsdraws the scale as whole blocks that fade on and off; any reading above the floor lights at least one.valueLabeltakes a function handed the judged reading and itsregion, so the judgement is put into words that are drawn and spoken alike rather than left to colour. One accessible element with theprogressbarrole, spoken the wayProgressspeaks its value.delacour add metercopies it into a project, along withprogress.
- Built on
-
#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.TrackandProgress.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.isIndeterminatesweeps 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 onProgress.Output, orvalueLabelword the readout. - One accessible element with the
progressbarrole: 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 progresscopies it into a project.
- Compound anatomy:
-
#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.Itemwraps any row, avatar or swatch with a roundleadingmark, aringor nothing;.Headercounts the picks and offers select-all and the way out;.Barand.Actionact on the selection;.Grouplays items out as a stacked card, a grid or a horizontal strip. Selection is a set of ids, controllable alongside the mode, with an optionalmax.delacour add selection-modecopies it into a project. -
#135 Add
SlideButton, a control confirmed by dragging a handle across a railThe 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, andthreshold={1}demands the far end. Controlled, the handle waits at the end untilisCompletedanswers; a rejectedonCompletepromise takes it home. Variantssecondary,destructiveandsuccess; sizes matchButton. 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.ActionsandStackCard.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.
directionspicks 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.layoutdraws the cards behind as astack, afanorflat,depthof 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
indexwhere 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-cardcopies it into a project.
- Compound anatomy:
-
#92 Add
AvatarA person as a picture. The initials of
nameare 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.variantandcolorpaint the fallback on the six coloursBadgetakes;sm,md,lgandxlare fixed edges.Avatar.Badgepins a count or, left empty, a presence dot to a corner without the circle clipping it.Avatar.Groupoverlaps its faces with the first on top, rings each in the page background, and counts the people pastmax— or up tototal— in a trailing+Ntile thatonOverflowPresscan make a button.delacour add avatarcopies 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/maxDateanddisabledmatchers (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, optionalSurfacechrome, custom day content, andisInvalid/isDisabled/ the label cascade from aField. Days are plainCalendarDates —{ 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 calendarcopies it. -
#117 Add
isMotionCalmtoDelacourProvider, anduseCalmMotion()An E2E build passes
isMotionCalmand 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 shimmeringSkeleton. Motion that is the behaviour keeps moving:Spinnerand an indeterminateProgressare 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 — andCalmMotionProvidersets the app's half for a hand-composed root.SkeletonandSkeleton.Groupnow read it. -
#90 Add
Chip, an interactive pill for filters, tags and removable tokensWhat a chip does follows from its props. With no handler it is a plain tag; with
onPressit is a button; withisSelected,defaultSelectedoronSelectedChangeit is a filter that toggles — controlled or uncontrolled, with aselectionhaptic, announced to a screen reader as selected or not.onCloseadds 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 unselectedsoftoroutlinechip 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, anduseChip()for a child that restyles itself on selection.bunx delacour add chipcopies it in, along withbadge, whose tones it reads.Pressablenow multiplies aclassName's opacity into its press feedback instead of overwriting it, so a disabledButton,BadgeorChipfades 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), withCollapsible.Trigger,.Title,.Description,.Indicatorand.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. SharesAccordion's variants, sizes and motion, andbunx delacour add collapsiblecopies it without pulling in an accordion. -
#136 Add
Fab, one primary action floating over the screen it belongs to.placementpins 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, inprimary,secondary,surfaceanddestructive.isExtendedwithFab.Labelmakes a labelled stadium.Fab.Groupunfolds a dial ofFab.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 takesisOpen/defaultOpen/onOpenChange.delacour add fabcopies it in. -
#93 Add
Item, a row of media, text and actions for lists and settings. It renders as aPressablewhen givenonPressand as a plain view otherwise, hasdefault,outlineandmutedsurfaces,sm/md/lgsizes, averticalorientation for cards,isDisabledandisSelectedstates, andItem.Media(bare icon, icon tile or clipped image),Item.Content,Item.Title,Item.Description,Item.Actions,Item.Header,Item.Footer,Item.GroupandItem.Separator. Dropped into aListGroupit takes the group's size, draws no surface of its own and lines up with the group's dividers. The CLI can nowadd item. -
#85 Add
Label, a form control's name with required, invalid and disabled statesLabelrendersText.Labeland takes a colour, never a scale, so it keeps the type scale the rest of a form uses.isRequiredappends 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.isInvalidturns it destructive andisDisabledfades it and reports it disabled to assistive technology.import { Label } from "@delacour/react-native-ui/label"; <Label isRequired>Email</Label>;bunx delacour add labelcopies it in, bringingtextandtvwith it. -
#138 Add
ProgressButton, a button held rather than tapped to confirm. A fill grows from the leading edge overholdDuration(2000 ms by default) andonCompletefires 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 apillorroundedshape, draws a tick on completion or a caller'sProgressButton.Done, rewinds by travelling back withisAutoResetor a controlledisCompleted, steps in fifths under reduced motion, and completes on a screen reader's activate.delacour add progress-buttoncopies 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.allowClearlets a tap on the value held withdraw it,Rating.Outputshows the value, and the row is one adjustable control to screen readers. Six colours, three sizes, andisInvalid/isDisabledcascade from aField.bunx delacour add ratingcopies it. -
#96 Add
Skeleton, a placeholder for content that is still loadingIt comes in three shapes —
rect,lineandcircle— 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 whenisLoadingflips off with nothing moving.Skeleton.Linesdraws a paragraph, andSkeleton.Groupkeeps 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 alabel.bunx delacour add skeletoncopies it in. -
#99 Add
Steps, a stepper for multi-step flowsStepsshows where someone is in a flow: numbered indicators joined by a line, each step completed, current or upcoming against one zero-basedvalue. 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 itsSteps.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
completedoverrides whether it reads as done. Steps take presses and move the value there;isLinearkeeps the steps ahead closed, and a controlledvaluewith noonValueChangeis a read-only progress display whose steps are not announced as buttons.Steps.Panelholds 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 stepscopies it into your project. -
#89 Add
Textarea, a multiline text field sized in rows. It rendersInput's box — variants, sizes, focus, invalid, disabled and theFieldcascade — and adds a height derived fromrowsand the size's line height,autoGrowup tomaxRows, and ashowCountcharacter count againstmaxLengththat turns destructive at the limit.delacour add textareacopies it, withinputandtextas registry dependencies. -
#84 Add
ToggleButton, a button that stays pressed. On its own it is controlled withisSelectedandonSelectedor holds its own state fromdefaultSelected; insideToggleButton.Groupthe group owns one array of selected values, inmultipleorsingleselection mode, withisSelectionRequiredto 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 likeButton.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-buttoncopies it in. -
#35
initcopies the root provider in anddoctoracceptsDelacourProvideras the gesture rootdelacour initnow adds theprovideritem alongsidestyles, soDelacourProvideris in theuidirectory before anything is mounted, and its follow-up list names that import rather than a bareGestureHandlerRootView.doctor's Gesture Handler check passes on either name, reads a blank template's rootApp.tsxas well asapp/andsrc/, and no longer counts the copiedprovider.tsxitself as the app mounting it. -
#49
addsets the project up itselfaddno longer fails on a project with nonative-components.json. It runsinitfirst — 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 hitMissingConfigErrorwhile a human sailed past.--no-initis the opt-out, and-s, --src <dir>forwards toinitso a template that keeps its files at the project root still needs one command.initnow returns the result of theaddit 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 notsrc.add_componentsneeds it no more often than a person needsinit. -
#25 One theme file, the same shape everywhere
theme.cssis now the one file instyles/that is yours, and its header says so. Whatdelacour initships, what the docs site's/themepage emits with no preset, and whatdelacour themewrites are held to the same shape, declaration for declaration.delacour themeconvertstheme.cssin place. Paste a shadcn or tweakcnglobals.cssover 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 doctorfails on atheme.cssstill in shadcn's:root/.darkshape, which Uniwind reads as a utility class nameddarkand a dark theme that never arrives.The converter fills what shadcn v4 stopped declaring.
--destructive-foreground— whichButton,Badge,Switch,SliderandCheckboxall paint with — and the shadow scale are now derived when a source omits them, and the light--elevatedderivation 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 initwrites today, and the default typeface is each platform's own sans rather than Geist, which a fresh app had never loaded. The/themepage showstheme.cssfirst, with shadcn'sglobals.cssin a second tab for a web app sharing the theme. -
#51
delacour skillsinstalls an agent skillAn 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 installroute for the native modules underneath.bunx delacour@alpha skillsinstalls 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 projectoruser. 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,addanddoctorinstead, and spends its own words on the failures that produce no error message.
Patch changes
-
#117 Fix
AccordionandCollapsiblepanels that never opened on their first expand in Release buildsThe 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
onLayouthad 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 pureaccordionTravelTarget/collapsibleTravelTarget, and a test sweeps both components for any JS-thread read of the height. -
#94 Add
Alert, a status message on aSurfaceFive statuses —
default,info,success,warninganddestructive— 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 fromAlert.Indicator,Alert.Content,Alert.Title,Alert.Description,Alert.ActionandAlert.CloseButton;isDismissibleadds the close control, uncontrolled or throughisOpen/onOpenChange, anduseAlert().dismisscloses it from an action.bunx delacour add alertcopies it into a project, withsurfacealongside. -
#95 Add
Card, a content surface with a header, a body and a footerBuilt 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.ContentandCard.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.Actionis pinned to the header's corner wherever it is written, the title follows the foreground token of the card's fill, andCard.Footer variant="band"sets the footer into the card on the next fill down.useCard()exposes the size and fill to custom parts, andbunx delacour add cardcopies it into a project, withsurfacealongside. -
#116
add bottom-sheetinstalls the engine instead of the library it replaced:@delacour/react-native-bottom-sheetfrom npm, andreact-native-teleportthroughexpo install, because it ships a Fabric portal view and needs a rebuild.@gorhom/bottom-sheethas left the install map,doctorwatchesreact-native-teleportfor a duplicated copy in a monorepo, and its New Architecture failure now namesBottomSheet, 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@alphaand@delacour/react-native-charts@alpha, and a stable CLI adds them untagged fromlatest. 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, anddelacour add dialog. A compound root withTrigger,Content,Close,Header,Title,Description,BodyandFooter, drawn through the overlay foundation, so it sits above every bottom sheet and the navigator's header.isOpen/defaultOpen/onOpenChange, andisDismissible={false}for an alert dialog that only its own actions close. Four sizes (sm,md,lg,full), aplainorpanelfooter that stacks onsm, 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, anddelacour add drawer. A compound root withTrigger,Content,Header,Title,Description,Body,FooterandClose, drawn through the overlay foundation, so it sits above every bottom sheet and the navigator's header. Four sides —startandendswap under RTL, plustopandbottom— 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
EmptyStateA placeholder for a list or screen with nothing in it.
EmptyState.HeaderstacksMedia,TitleandDescription, andEmptyState.Contentholds the actions.variant="card"draws a dashed block for an empty section beside populated ones, andsizescales the padding, media and type together. A bareIconinMediainherits its size and colour, andMedia variant="icon"tints a box behind it.delacour add empty-statecopies 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, anddelacour add feedback. Built onDialog, so open state, portal, scrim, back, escape, focus and the keyboard lift are Dialog's. A compound root withTrigger,Content,Panel,Title,Close,Field,Footer,Action,CancelandSubmit, anduseFeedback(). The draft isvalue/defaultValue/onValueChangeon the root and survives close and reopen untilclear().Submitis gated bycanSubmitFeedback(empty and whitespace are empty,canSubmitEmptyfor 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 fromminRows(6) tomaxRows(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 thereBuilt on
Card, so it takes the card's four fills and three sizes and sits on the card's inset.Kpi.Trendtakes a signed percentage and colours it by what it means rather than by its sign —goodDirectionon 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.Sparklineis aChartline 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 thatuseKpi()reads.isLoadingholds placeholders of each part's size, andKpi.Grouplays several metrics out in a row or a column, optionally on one surface with rules between.ChartgainsframeClassName, which sizes the plot's frame.bunx delacour add kpicopies it into a project, withcardandchartalongside. -
#120 Add
OverlayProvider, the layer every overlay is drawn on@delacour/react-native-ui/overlaymounts 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) anduseOverlayBackHandlerare exported for building more.BottomSheetProvidernow detects anOverlayProviderabove or below it and mounts teleport's provider only once; the engine's provider takeshasPortalProviderto make that possible.bunx delacour add overlaycopies 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,useAnchoredContentandAnchoredArrow— are exported for other anchored overlays.delacour add popovercopies it. -
#83 Add
Surface, a rounded container on the theme's fill ladderFour fills —
default,secondary,tertiaryandtransparent— 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, andpadding="none"clips, for content bled to the corners.bunx delacour add surfacecopies it into a project. -
#122 Add
Toast— a brief message shown withtoast()from anywhere, including outside React, and drawn by one<ToastViewport />mounted insideOverlayProvider. 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 areAlert's;toast.promiseturns one loading toast into the success or the failure in place;rendercomposes 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 thealert-glyphs.tsleaf so the two share them.delacour add toastcopies it. -
#128 Add
Tooltip— a short label anchored to its control that opens on a long press (or a press, withopenOn="press"), hides itself afterduration, and closes on any outside tap while letting that tap through to what it landed on. It flips and shifts likePopover, has an inverted and a surface variant, an arrow that points at the trigger, and reads itslabelto a screen reader on the trigger without opening.OverlayProvidernow hears every touch that starts beneath it, throughsubscribeTouchStart, without claiming any.delacour add tooltipcopies it. -
#48 Publish the libraries under the
@delacourorgdelacour-react-native-uiis now@delacour/react-native-ui, anddelacour-react-native-chartsis now@delacour/react-native-charts. The old names are deprecated and take no further versions. (Both were briefly spelled@delacour/native-uiand@delacour/chartsin 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 chartnow installs@delacour/react-native-charts. -
#22 Add bar, scatter, candlestick and pie charts, stacked areas and horizontal bars
@delacour/react-native-chartsgains four marks and a second root.ChartBardraws 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 onestackIdstack in data space, so the y domain covers the running totals rather than the tallest series.ChartAreatakes the samestackId.ChartScatteris one Skia path per series, andChartCandlestickdraws 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.PolarChartis the new root, withPieSliceson 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/coreexports the bar, scatter, candle and slice geometry alongside the scales.@delacour/react-native-ui/chartskins all of it.Chart.Bar,Chart.ScatterandChart.CandlestickjoinChart.LineandChart.Area; bars group by being siblings, stack by sharing astackId, round their value end from--radius, and takelabels. Candles borrowsuccess,destructiveandmuted-foregroundfor their sentiment. Over bars or candlesChart.Tooltip.Xbecomes a band one step wide.PieChartis a second root —PieChart.Slice,.Label,.Center,.Tooltipand.Legend— whose categories are its rows, withinnerRadiusfor 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-uniwindexample and never namesinit:adddoes that setup itself, so a reader learns one verb.The landing page's hero says
addtoo, 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.txtlives onui.delacour.co.nz, and the releases page no longer reports a version that was never published.docs-commands.test.tswalks the commander program and holds every copyabledelacour …in the repository to it — verb, long flags, positional arity, and whether a component named in an example is in the registry. It caughtadd --srcbefore a reader did. -
#72 List every native peer in the install command. The charts README's
expo installline named only Skia, anddelacour add chartskipped 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.mainnow moves only on a release, so unreleased components live ondevelop. -
#50
doctorstops failing correct Metro configs, and tells you a path you can pastewithUniwindConfig is not the outermost wrapper, when it was. The check required the export expression to begin withwithUniwindConfig, so it failed bothconst 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 entryrestated 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 bothdoctorandinitnow print — from one function.
-
#50 Adopt the Tailwind entry an app already has, and print the root layout
One entry, never two.
initpicked<src>/styles/global.cssunless a wrapped Metro config named another, so an app that had installed Uniwind and written its ownglobal.css— without wiring Metro to it yet — got a second entry. Tailwind then compiled the file holding the@sourceglobs while the app imported the one without them: every component unstyled, nothing logged, anddoctorreporting 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
initshows that file rather than describing it — and detects Expo Router, whose root layout is a different file with a different shape (app/_layout.tsxrendering a<Slot />, notApp.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.lockpinnedexpo-modules-jsi@57.0.5while a dependency already required~57.1.0, sobun install --frozen-lockfilefailed the moment anything forced a re-resolve. The patch that pinning existed for — declaringretainRuntimeScheduler/releaseRuntimeSchedulerfor Swift bridging — ships in57.1.0itself, so it and itspatchedDependenciesentry are gone. -
#49 Stop the last line suggesting a command that already ran
adddelegates toinitwhenever a project has no config, so a run that copiedbuttonin ended on "Ready.delacour add buttonto get started." — the command the reader had just run. The outro now names what landed instead: "Ready.buttonis yours to edit."It no longer mentions
doctoreither. The follow-up block directly above it already ends ondelacour 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
Chartcomponent that skins it@delacour/react-native-chartsis new — a token-free charting engine for React Native, drawn with Skia, animated with Reanimated and driven by Gesture Handler. It shipsCartesianChartwithLine,Area,Gridand 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/coreis every scale, tick, curve and solver in it, importable with no Skia in the module graph.@delacour/react-native-ui/chartis that engine wearing the theme. A shadcn-shapedconfignames each series and assigns--chart-1…--chart-5by 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.XAxisandChart.YAxisdraw into the canvas, whileChart.TooltipandChart.Legendare 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-skiais a native module and is new to the workspace — runexpo prebuild --cleanand rebuild before running the playground.The CLI learns two things: how to install a Skia-backed component, and that
@delacour/react-native-chartspublishes to thealphatag while this repository is in pre mode, since a barebun addof it would resolvelatestand find nothing. -
#50 Work correctly on a project that already has Uniwind
Three bugs, all found against Expo's
with-router-uniwindexample — a scaffold that arrives with Uniwind, Tailwind and Metro already wired.initwrote its@sourceblock into a file Metro does not compile. A wired Metro config names its owncssEntryFile, andinitrecorded<src>/styles/global.cssregardless — 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 readscssEntryFileanddtsFileoff the wrapped config and records those.doctorfailed the official template. Its outermost-wrapper check required the export expression to begin withwithUniwindConfig, 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.initasked 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.initnow warns before it touchesmetro.config.js, anddoctorcarries aStylingcheck 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.mainnow moves only on a release, so unreleased components live ondevelop.
0.1.0-alpha.5
Patch changes
- #72 List every native peer in the install command. The charts README's
expo installline named only Skia, anddelacour add chartskipped Gesture Handler — the scrub and the pie's tap then never fire, with no error.
0.1.0-alpha.4
Minor changes
-
#49
addsets the project up itselfaddno longer fails on a project with nonative-components.json. It runsinitfirst — 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 hitMissingConfigErrorwhile a human sailed past.--no-initis the opt-out, and-s, --src <dir>forwards toinitso a template that keeps its files at the project root still needs one command.initnow returns the result of theaddit 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 notsrc.add_componentsneeds it no more often than a person needsinit. -
#51
delacour skillsinstalls an agent skillAn 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 installroute for the native modules underneath.bunx delacour@alpha skillsinstalls 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 projectoruser. 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,addanddoctorinstead, 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-uniwindexample and never namesinit:adddoes that setup itself, so a reader learns one verb.The landing page's hero says
addtoo, 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.txtlives onui.delacour.co.nz, and the releases page no longer reports a version that was never published.docs-commands.test.tswalks the commander program and holds every copyabledelacour …in the repository to it — verb, long flags, positional arity, and whether a component named in an example is in the registry. It caughtadd --srcbefore a reader did. -
#50
doctorstops failing correct Metro configs, and tells you a path you can pastewithUniwindConfig is not the outermost wrapper, when it was. The check required the export expression to begin withwithUniwindConfig, so it failed bothconst 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 entryrestated 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 bothdoctorandinitnow print — from one function.
-
#50 Adopt the Tailwind entry an app already has, and print the root layout
One entry, never two.
initpicked<src>/styles/global.cssunless a wrapped Metro config named another, so an app that had installed Uniwind and written its ownglobal.css— without wiring Metro to it yet — got a second entry. Tailwind then compiled the file holding the@sourceglobs while the app imported the one without them: every component unstyled, nothing logged, anddoctorreporting 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
initshows that file rather than describing it — and detects Expo Router, whose root layout is a different file with a different shape (app/_layout.tsxrendering a<Slot />, notApp.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.lockpinnedexpo-modules-jsi@57.0.5while a dependency already required~57.1.0, sobun install --frozen-lockfilefailed the moment anything forced a re-resolve. The patch that pinning existed for — declaringretainRuntimeScheduler/releaseRuntimeSchedulerfor Swift bridging — ships in57.1.0itself, so it and itspatchedDependenciesentry are gone. -
#49 Stop the last line suggesting a command that already ran
adddelegates toinitwhenever a project has no config, so a run that copiedbuttonin ended on "Ready.delacour add buttonto get started." — the command the reader had just run. The outro now names what landed instead: "Ready.buttonis yours to edit."It no longer mentions
doctoreither. The follow-up block directly above it already ends ondelacour 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-uniwindexample — a scaffold that arrives with Uniwind, Tailwind and Metro already wired.initwrote its@sourceblock into a file Metro does not compile. A wired Metro config names its owncssEntryFile, andinitrecorded<src>/styles/global.cssregardless — 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 readscssEntryFileanddtsFileoff the wrapped config and records those.doctorfailed the official template. Its outermost-wrapper check required the export expression to begin withwithUniwindConfig, 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.initasked 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.initnow warns before it touchesmetro.config.js, anddoctorcarries aStylingcheck 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
@delacourorgdelacour-react-native-uiis now@delacour/react-native-ui, anddelacour-react-native-chartsis 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 chartnow installs@delacour/react-native-charts.
0.1.0-alpha.2
Minor changes
-
#35
initcopies the root provider in anddoctoracceptsDelacourProvideras the gesture rootdelacour initnow adds theprovideritem alongsidestyles, soDelacourProvideris in theuidirectory before anything is mounted, and its follow-up list names that import rather than a bareGestureHandlerRootView.doctor's Gesture Handler check passes on either name, reads a blank template's rootApp.tsxas well asapp/andsrc/, and no longer counts the copiedprovider.tsxitself as the app mounting it.
0.1.0-alpha.1
Minor changes
-
#25 One theme file, the same shape everywhere
theme.cssis now the one file instyles/that is yours, and its header says so. Whatdelacour initships, what the docs site's/themepage emits with no preset, and whatdelacour themewrites are held to the same shape, declaration for declaration.delacour themeconvertstheme.cssin place. Paste a shadcn or tweakcnglobals.cssover 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 doctorfails on atheme.cssstill in shadcn's:root/.darkshape, which Uniwind reads as a utility class nameddarkand a dark theme that never arrives.The converter fills what shadcn v4 stopped declaring.
--destructive-foreground— whichButton,Badge,Switch,SliderandCheckboxall paint with — and the shadow scale are now derived when a source omits them, and the light--elevatedderivation 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 initwrites today, and the default typeface is each platform's own sans rather than Geist, which a fresh app had never loaded. The/themepage showstheme.cssfirst, with shadcn'sglobals.cssin 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-chartsgains four marks and a second root.ChartBardraws 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 onestackIdstack in data space, so the y domain covers the running totals rather than the tallest series.ChartAreatakes the samestackId.ChartScatteris one Skia path per series, andChartCandlestickdraws 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.PolarChartis the new root, withPieSliceson 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/coreexports the bar, scatter, candle and slice geometry alongside the scales.@delacour/react-native-ui/chartskins all of it.Chart.Bar,Chart.ScatterandChart.CandlestickjoinChart.LineandChart.Area; bars group by being siblings, stack by sharing astackId, round their value end from--radius, and takelabels. Candles borrowsuccess,destructiveandmuted-foregroundfor their sentiment. Over bars or candlesChart.Tooltip.Xbecomes a band one step wide.PieChartis a second root —PieChart.Slice,.Label,.Center,.Tooltipand.Legend— whose categories are its rows, withinnerRadiusfor 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
Chartcomponent that skins it@delacour/react-native-chartsis new — a token-free charting engine for React Native, drawn with Skia, animated with Reanimated and driven by Gesture Handler. It shipsCartesianChartwithLine,Area,Gridand 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/coreis every scale, tick, curve and solver in it, importable with no Skia in the module graph.@delacour/react-native-ui/chartis that engine wearing the theme. A shadcn-shapedconfignames each series and assigns--chart-1…--chart-5by 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.XAxisandChart.YAxisdraw into the canvas, whileChart.TooltipandChart.Legendare 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-skiais a native module and is new to the workspace — runexpo prebuild --cleanand rebuild before running the playground.The CLI learns two things: how to install a Skia-backed component, and that
@delacour/react-native-chartspublishes to thealphatag while this repository is in pre mode, since a barebun addof it would resolvelatestand find nothing.