UI release notes
What changed in each release of @delacour/react-native-ui, from the changesets that shipped it.
Every version of @delacour/react-native-ui. The component library. It shares one version with the other Delacour packages — see Release channels.
0.8.0
Patch changes
-
#331
Input.Groupnow bleeds only its outermost blocks through the box padding: the firststartblock and the lastendblock. Before, every block in a section took the negative margin, so a second block was pulled over its separator. -
#331 An inline modal, or one in a custom host, no longer makes
OverlayProviderhide the app content from VoiceOver and TalkBack. The contentViewcan hold that modal, so hiding it hid the modal too. Stacking and the back button are unchanged. -
#331 Drop the
unique symbolannotation on the overlay teleport flag. Aconstinitialised withSymbol.foralready has a unique symbol type, and the annotation broke source scanners that parse the file with a plain JavaScript grammar. -
#333
Select.Trigger asChildnow passes itsclassNameto the child. Before, the classes were dropped. -
#333
Select.Trigger asChildno longer passesdisabled={false}to its child when the select is enabled, so a child that is disabled on its own (<Button isDisabled>) stays disabled and never opens the sheet. -
#333
Selectdraws the sheet close button in its own row with the optional title. Before, with no title, the absolutely positioned close sat over the search field or the first option. -
#333 A controlled
Selectnow clears its search query when the owner setsisOpento false. Before, only a close from inside the sheet cleared it, so the next open showed the old filtered results. -
#333 The
Selectsearch field no longer takesisDisabledorisInvalidfrom an enclosingField. An enabled select inside a disabled field can now be searched, and an invalid value no longer marks the query invalid. -
#331
Slotnow throws for a fragment child. A fragment passesisValidElementbut drops the props it is given, so anasChildtrigger around a fragment did nothing.
0.7.0
Minor changes
- #320
DatePickertakesdefaultMonth,startMonthandendMonthand passes them to its calendar, so the default anatomy can open near a likely date and bound paging and the year list. Its docs add a date of birth example withminDateandmaxDate.
Patch changes
-
#319
Avatarfalls back to the accessible name "Avatar" when it is pressable and has noname,fallbackoraccessibilityLabel, so it is never read as only "button". Initials keep their accent when the name arrives in NFD form. -
#319
Avatar.Badgedraws the presence dot when its children are0or"", such as{unread && <Badge />}with no unread items, instead of throwing on a raw string in aView. -
#319
Avatarremounts its image when the source changes, so a late error from an expired URL no longer marks the refreshed one as failed. -
#319
Avatarretries its image when the entries of a multi-resolution list change theirwidth,heightorscale, and, for a list, when the avatar changes size.imagePropsno longer acceptssrcorsrcSet, which would overridesourceand bypass the retry key. -
#323 Android back closes an overlay open over a
BottomSheet, not the sheet. Under anOverlayProvider, the sheet turnscloseOnBackoff while a modal or anchored overlay is open. -
#323
BottomSheet.Containerfinds aHandleorBackgroundwritten inside a fragment.<>{showHandle && <BottomSheet.Handle />}</>no longer gets a second handle and background added, which gave screen readers two adjustable elements. -
#323 A
BottomSheetwith anOverlayhides the app behind it from VoiceOver and TalkBack while it is presented in the root host.BottomSheetProviderhides its children, asOverlayProviderdoes for a dialog. Inline sheets, sheets in anotherBottomSheet.HostandenableTouchThroughleave the app reachable. -
#324
Calendar.Captionnow reads an enclosingField.Labelbefore the month ("Date of birth, October 2026"). The label used to sit only on the grid, which is notaccessibleand gets no iOS trait fromrole="grid", so VoiceOver never read it.resolveCaptionLabelis exported from the variants. -
#324
Calendar.Daynow puts "Today", "Range start" and "Range end" in its accessibility label ("Monday 5 October 2026, Today, Range start") instead of the hint, so they are read with VoiceOver hints off.resolveDayAccessibilityLabelis exported from the variants. -
#324 A read-only
Calendarno longer announces every day as disabled ("dimmed"). A read-only day keeps an enabled accessibility state, its tap is still ignored, and it skips the press fade and haptic.CalendarDayStategainsisUnavailablefor the days that are announced disabled. -
#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'". -
#320
DatePickerstays controlled whenevervalueis passed, even asundefined, which now reads as empty. A form field that startsundefinedor resets to it no longer flips the picker to its own state and leaves a stale day on the trigger. -
#320 A read-only
DatePicker.Triggerreads as text instead of a button. It has no press handler, so VoiceOver no longer offers a double-tap that does nothing. A disabled trigger keeps the button role. -
#320 A re-tap on a single
DatePicker's shown day now confirms it and closes the sheet instead of clearing it. A user who opens a date of birth to check it no longer empties it by tapping it.DatePicker.Clear(isClearable) stays the way to empty a single day. -
#324 A modal overlay that is animating closed no longer hides the app content from screen readers, so a Dialog or Drawer that moves focus back to its trigger in
onExitedlands on it.Overlay.PortaltakesisExiting; passuseOverlayPresence().phase === "exiting". -
#324 Screen readers stay inside the top modal overlay. The top open modal's portal frame is modal for VoiceOver, and every overlay drawn under it is hidden from VoiceOver and TalkBack, so a dialog opened over another dialog no longer leaves the lower one reachable.
topModalOverlayandisBelowTopModalare exported fromoverlay. -
#325
TextAnimationfixes for first-frame painting, negative numbers and font scaling:Rollingpicks its roll direction from the magnitude, so a negative number such as −19 → −20 rolls the short way.- New reveal text holds its start until the clock has rewound, so it never paints a frame fully shown before it reveals.
- A
Rotatingswap holds its start until progress has rewound, so the arriving phrase never flashes in fully shown. RollingandRotatingcap font scaling atTEXT_MAX_FONT_SIZE_MULTIPLIERby default, like the other parts.
0.6.0
Minor changes
-
#312
SignatureandSketch'stoPNG()now returnPromise<string>. react-native-skia 3.9 madeImage.encodeToBase64asynchronous, and the peer range moves to>=3.9.0—awaitthe call. -
#318
DatePickertakes adescription, shown under the sheet's title as aBottomSheet.Description({testID}-description), so a picker can say why some days cannot be picked. -
#293
DatePickertakesonClose, called once its sheet has slid all the way down.onOpenChange(false)still fires as the close starts, so a form can move focus to the next field inonClosewithout the keyboard rising under a sheet that is still leaving. -
#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. -
#306
SelecttakesisOpen,defaultOpenandonOpenChangeto control its sheet, asDatePickerdoes, andonClose, called once the sheet has slid fully closed after a pick or a dismissal. A form can now open the select when focus moves to it, and move on only after the sheet has left. -
#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. -
#290
TextAnimation.CountingandTextAnimation.Rollingtakeprefixandsuffixprops, which win overformat's.Rollingkeeps each affix as one static cell, so a digit inside one (/100,Q3) no longer rolls as a wheel.
Patch changes
-
#310
Avatar.Groupcounts each avatar inside a fragment as its own face, somaxand the+Ntile work when the faces come from a fragment or a mapped list wrapped in one. The newflattenAvatarChildrenhelper opens fragments at any depth and keys each face as a[fragmentKey, faceKey]JSON pair, so keys stay unique however the fragments are keyed. -
#310
Avatarscales a bundled image (source={require(...)}) to fill the face. The image slot addssize-full, becauseinset-0does not override the width and height a bundled asset brings, so a large asset was clipped. -
#310
Avatarretries its image when any request field changes, the cache mode included. The retry key is now a JSON array of the URI, bundle, method, sorted headers, body and cache mode, so a header value with&or=no longer shares a key with a different request. -
#298
BottomSheet.Closereaches the 44pt minimum touch target.BOTTOM_SHEET_CLOSE_HIT_SLOPis now 12 instead of 8: the 20pt default icon plus 12pt on each side is 44pt, where 8pt left a 36pt target. -
#298
BottomSheethonoursonSnapPointHaptic={undefined}andonCloseHaptic={undefined}as the documented way to turn a haptic off. A destructuring default had put the Pulsar preset back in place of the explicitundefined. -
#297 The screen-reader increment and decrement actions on
Calendar.Captionno longer page a disabled calendar or the years view, and no longer fireonMonthChangethere. They now have the same limits as the arrows. -
#307 When a parent changes
Calendar's controlledmonth, the new month appears without a slide. Before this fix, the last paging direction stayed set. For example, after the user tapped next, a date picker that reopened on an earlier month slid it in from the right. -
#297
Calendarand its date helpers always format in the Gregorian calendar that the grid uses. A locale such asfa-IRchanges the language and digits, not the calendar. Before this fix, 5 October 2026 was labelled 13 Mehr 1405, and the month picker's names did not match the months they selected. -
#297 Selecting an outside day with
selectOutsideDaysno longer pagesCalendarpaststartMonth/endMonth(orminDate/maxDate). The day is still selected, but the grid stays inside the bounds that the arrows and the swipe keep. -
#297
Calendar's years view scrolls to the visible year each time it opens. Before this fix, if you picked a year and then reopened the years view from the months view, the list started at the earliest year. -
#297
Calendarmoves the visible month to the nearest bound whenminDate/maxDateorstartMonth/endMonthchange and leave it outside, and reports the move throughonMonthChange. Before this fix, movingminDatepast the visible month disabled both arrows and the swipe, and the calendar could not reach a day that can be picked. -
#297
Calendar.Gridslides the current month back into view when a swipe's month change is not committed. This happens when a parent holds the controlledmonthstill or passes noonMonthChange. Before this fix, the grid stayed off-screen and the calendar looked empty. -
#307
resolveWeekStartreads the device locale on engines withoutIntl.Locale, such as some Hermes builds. Before this fix, aCalendarwith nolocaleon such an engine started the week on Monday, even on an en-US or ja-JP phone. -
#297
Calendar's years view lists the year the calendar shows. The open side of the year span now reaches from the visible month, not from today, and never past the opposite bound. A calendar with onlymaxDatein 1900 lists 1800–1900, where the list was empty before. -
#295
DatePickercancels the pending auto-close on every selection. Starting a new range, or deselecting the day, within 120 ms of a finished selection no longer closes the sheet on an unfinished or empty selection. -
#295
DatePickerdrops a half-picked range and cancels any pending auto-close when a controlled caller closes the sheet withisOpen={false}. The trigger no longer shows the half range after the sheet closes, and the range does not resume when the sheet opens again. Inline pickers are unaffected. -
#308
DatePicker.Triggerreads a read-only picker as enabled. OnlyisDisabledsetsaccessibilityState.disablednow, so VoiceOver no longer reads a read-only value as dimmed. A trigger that cannot open drops the "Opens calendar" hint, its press handler and its press feedback. -
#309 While a modal overlay is open, the app content behind it is hidden from screen readers (
accessibilityElementsHiddenon iOS,importantForAccessibility="no-hide-descendants"on Android), so VoiceOver and TalkBack focus stays inside the overlay. -
#296 Overlays no longer get stuck when
isOpenflips while an enter or exit animation's finish is still queued: a stale finish is dropped before it moves the phase or callsonEntered/onExited. Under reduce motion the scrim and content now fade instead of snapping. -
#309 Overlay presence timing no longer restarts when the owner re-renders with new inline
onEntered/onExitedcallbacks: they are read through refs, so onlyisOpendrives the enter and exit animations. -
#311
Spinneris now one accessibleprogressbarelement, so a screen reader announces it once with its label instead of reaching its inner views separately. -
#311
Pressable asChildgiven a text child now throws its documented error ("text and fragments cannot take props") instead of React's genericChildren.onlymessage. -
#311 Fix form controls reacting while disabled, found by their new render tests:
Checkboxno longer flips when itsFieldrow is pressed while the checkbox is disabled.OtpInputno longer takes focus from aFieldrow press while it is disabled.NumberInput.FieldandSlider.Thumbignore screen-reader increment and decrement actions while disabled.Sliderno longer callsonChangeon mount, and calls it once per screen-reader step instead of twice.
-
#311 Fix screen-reader gaps found by the overlay render tests:
MenuandContextMenurows can be chosen with VoiceOver and TalkBack. A row now answersonAccessibilityTapon iOS and theactivateaction on Android, and a disabled row still ignores both.Screen's navbar title andScreen.Error's title are announced as headings.- The render-test setup marks
AppStateas active. React Native's Jest mock otherwise reads as backgrounded, which kept a toast from ever dismissing itself. - A composed
ImageViewer(Triggers as children, noimages) opened withdefaultOpenor a controlledopenno longer shuts itself on mount before its Triggers register.
-
#311 Tabs: a bar with no
Tabs.Contentand nodefaultValuenow opens on its first tab, as documented. It used to start with nothing selected, because its order was still empty when the initial value was seeded. -
#311
Sortable.Itemignores a Move up / Move down accessibility action on a row that cannot be dragged — a disabled, list-disabled or pinned row. Those rows advertise no actions, but one sent anyway used to reorder them. -
#311
Post.AuthorandPost.Communityhide theirAvatarfrom assistive technology. The parent's one-stop label already names the person or community, so the labelled avatar inside it was announced a second time on Android. -
#298
asChildkeeps the handler it donates when the child forwards an unset one.<BottomSheet.Trigger asChild><Button onPress={maybe} /></BottomSheet.Trigger>withmaybeundefined now opens the sheet; before, the child'sundefinedreplaced the trigger'sonPress. -
#315
TextAnimationfixes for font scaling, retargeting and calm motion:Typing,Countingand the reveal give every text they draw the same font scale cap: cells, the block caret, whitespace and the copies that measure.Countingreserves its width from the number on screen.Rollingretargets each wheel from where it is now and rounds toward the roll's direction, so an interrupted roll keeps the new direction.- When calm motion turns on,
Rollingsettles its wheels andRotatingreturns to its first phrase. TypingcallsonPhraseTypedfor every phrase, even with no hold and no erase or when one frame finishes several, and stops counting the height of a removed phrase.- A
linereveal waits for its lines to be measured before it starts.onFinishedfires once per play, also when calm motion is turned on or off, and settingisActiveback totrueplays the reveal again. Rollingretargets a wheel whose direction flips on the same digit.Rotatingaligns its phrases inside the reserved slot and starts over when its phrase list changes.- The clock stops its frame callback once a run finishes.
- Rows follow the resolved text alignment, so
classNameand an inherited alignment count.capitalizecapitalizes each word, not each letter, inTypingand acharacterreveal. Empty lines from hard breaks keep their height. - The block caret's fill is generated in every colour.
0.5.0
Minor changes
-
#252
Flowdraws its edges, background, connection preview and minimap with Skia instead ofreact-native-svg, soreact-native-skiais now an optional peer it needs, asChartandSketchdo. The background is a runtime shader that stays sharp at any zoom.useThemeColorsresolves several theme colours in one lookup. -
#285
ScrollFadetakeskeyboardAware, which lifts the end fade above an open keyboard and keeps it there while the content scrolls. Without it the end fade sat under the keyboard, and over aKeyboardAwareScrollViewit cleared for good after the first scroll.scrollEdgeOpacitytakes an optionalcoveredEnd, andresolveKeyboardLiftis the new pure resolver behind the lift. -
#249 Signature: the pad draws its strokes with Skia instead of
react-native-svg— the finished strokes as one recorded picture and the stroke under the finger as a path traced on the UI thread, with no path string built and parsed per frame.traceStrokeis exported for drawing a stroke into a Skia path builder;smoothStroke,toSVG()andtoPaths()are unchanged.react-native-skiais now needed bySignature.Signature: the ref gains
toPNG(), the signature as a base64 PNG framed and coloured exactly astoSVG()with the same options, atscalepixels per point.resolveSignatureExportandsignaturePngSizeare exported. -
#251 Move to
react-native-skia3, the package@shopify/react-native-skiawas renamed to. The optionalreact-native-skiapeer replaces@shopify/react-native-skiaand needs>=3.7.3, which needs AndroidminSdkVersion26.Sketch'stoPNG()renders onSkia.Surface.MakeRaster. -
#281 SlideButton takes
isLoading: the handle's glyph gives way to a spinner, the drag is refused and the control announces itself busy —Button'sisLoading, on the handle.
Patch changes
-
#265 An
Attachmentchip stretched wider than its content — a vertical composer tray — now fills its width, so the remove cross sits at the far edge instead of beside the name.A chip's upload bar is clipped to the chip's rounded corner, so the end of an indeterminate sweep no longer hangs below the curve.
-
#269
Comboboxopens its sheet at its cap and holds that height while it is open, instead of sizing it to the filtered rows — typing no longer resizes the sheet, and the keyboard arriving no longer moves it.resolveComboboxSheetHeightis the new pure resolver behind it. -
#269
Comboboxkeeps its keyboard up while the results scroll; only a drag that carries the sheet down, or a close, puts it away. The sheet's scrollables now take a caller'skeyboardDismissModeinstead of always usinginteractive. -
#278
BottomSheettakeskeyboardLiftLimit="highestSnapPoint": the keyboard lifts the sheet no higher than its highest snap point, so a sheet resting there holds still and its body shrinks by the keyboard instead.Comboboxopens at its full height with it, so its sheet no longer opens half-height, jumps up when the search field's keyboard rises, and drops back as the keyboard leaves on close.resolveComboboxSheetHeighttakes onlytopInsetandwindowHeightnow. -
#266
Fab.Group's actions now grow out of the trigger and collapse back into it along its centre line, instead of scaling about the row's centre and swinging in from the label's side. -
#283
Inputtakes the enclosingField.Label's text as itsaccessibilityLabelwhen none is given, so<Field><Field.Label>Email</Field.Label><Input /></Field>announces "Email" to a screen reader instead of an unnamed text field. An explicitaccessibilityLabelstill wins.Textareainherits it. -
#261
Input.Group.Blockno longer stacks the box's vertical padding on its own. A toolbar above the field now sits as far from the border as from its separator, and a footer below it loses the extra space under its counter. -
#259 MarkdownEditor's preview keeps the write pane's corners: square top corners under a
bartoolbar, and the write pane's radius at every size. -
#284 Components no longer pass dependency arrays to Reanimated's
useAnimatedStyle,useDerivedValue,useAnimatedReactionanduseAnimatedScrollHandler. Reanimated ignores them on native and warned once per app in development: "Dependencies should only be used on the web and are always ignored on native". Nothing changes at runtime; the worklets still rebuild from their closures. -
#247 SectionProgress's pill now eases to each section's title width instead of jumping, and the titles slide out and in as the section changes — up for a later section, down for an earlier one. Reduce motion snaps the width and only fades.
useSectionProgress()gainsindex, the active section's position. -
#247 SectionProgress's pill now grows into its card as one surface — width, height and corners together, the list fading in as the card opens — instead of swapping a capsule for a scaled-in card. The card hugs a short list and stops at six rows, past which it scrolls. Reduce motion snaps the size and only fades the list.
-
#247
SectionProgressdraws its ring with Skia instead ofreact-native-svg: a track circle and a fill path trimmed to the progress shared value, with the same size, stroke, colours and twelve o'clock start.react-native-skia(already an optional peer) is now needed bysection-progress.resolveRingDashOffsetis replaced byresolveRingTrimEnd(the clamped 0–1 trim) andresolveRingOval(the stroke's centre-line box). -
#277 A searchable
Selectopens its sheet at its cap and holds that height while it is open, instead of sizing it to the filtered rows — typing no longer resizes the sheet, and the keyboard covers its bottom rather than moving its top. ASelectwithout search still sizes to its rows.resolveSelectListMinHeightis the new pure resolver behind it. -
#262
BottomSheettakeskeyboardLiftLimit. Set to"maxDynamicContentSize", a sheet sized to its content is lifted by a keyboard only as far as its cap, decided on the UI thread from the keyboard's first frame.Selectopts in, so a searchable select no longer shoots to the top of the screen when the keyboard opens and then settles back to its cap. -
#277 A searchable
Selectkeeps its keyboard up while the results scroll, asComboboxdoes; only a drag that carries the sheet down, or a close, puts it away. -
#251 Fix Skia canvases —
Chart,Flow,SectionProgress,Signature,Sketch,Tour— that came up blank after a JS reload (the dev menu's, orUpdates.reloadAsyncafter an OTA update) until something re-rendered them.react-native-skia3.7.3 allocates canvas ids natively, unique for the life of the process, so the optionalreact-native-skiapeer now needs>=3.7.3. -
#281 SlideButton's trail now fills the whole rail at the end of the travel instead of stopping under the handle's middle.
-
#248
Tourdraws its dimmed mask with Skia instead of react-native-svg: a window-sized rect with the spotlight clipped out, its rounded-rectangle hole derived on the UI thread from the existing shared values rather than rebuilt as a path string every frame.react-native-skiais now a peer ofTour.resolveMaskHoleis exported;buildMaskPathis deprecated and no longer used by the tour.Between steps the hole now crossfades — the old one fades closed while the new one opens — instead of sliding across the screen, and the card stays mounted and glides to its new place instead of blinking out and fading back in. Under reduce motion the card jumps; the hole still fades. Opening the tour now has an entrance: the dim fades up around the first hole and the card follows a beat later, rising a few points into place (reduce motion keeps the fade and drops the rise).
TOUR_CARD_FADE_MSis now 240.TOUR_HOLE_FADE_MS,TOUR_ENTER_MS,resolveTourHoleFadeDuration,resolveTourCardEntranceandisSameHoleRectare exported.
0.4.1
Patch changes
-
#238 Calendar: a
secondaryrange's start and end circles are one even colour — the band now runs under the whole circle instead of half of it. -
#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. -
#241 Item, ListGroup.Item and GridItem:
asChildtakes a single child element as written. The text wrap (and GridItem's background ordering) always rebuilt the children as an array, whichPressable'sChildren.onlyrejects even with one element, so<Item asChild onPress={…}><View /></Item>threw. UnderasChildthe children now pass through untouched, and without it a lone element comes back as that same element. The wrap is one shared helper,lib/wrap-text-children. -
#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. -
#240 Pressable: TalkBack's double-tap-and-hold now reaches
onLongPressthrough alongpressaccessibility action on Android, behind the samedisabled/busyguard as the press. Screen-reader activation and long press are dispatched bydispatchPressableAccessibilityAction, which fires each handler once and never on a gated control. -
#242 Export every public variant axis from its component's entry point.
BADGE_MATERIALS/BadgeMaterial,BUTTON_MATERIALS/ButtonMaterial,SURFACE_MATERIALS/SurfaceMaterialandSCROLL_SHADOW_ANCHORS/ScreenScrollShadowAnchorwere declared but only reachable by importing a component's internal files;@delacour/react-native-ui/text-animationnow also exportsTextAnimationEffectKind,TextAnimationCaretKind,TextAnimationSlideFromand a newTEXT_ANIMATION_SLIDE_DIRECTIONS. A package test now fails for any axis left unexported.
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 -
#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. -
#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. -
#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. -
#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
-
#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. -
#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. -
#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. -
#173 Add
Flow, a pan-and-zoom canvas of nodes joined by edges that route themselves — handles, groups, drag-to-connect, controls, a minimap and assistive move/connect/delete actions, all reporting to an app that owns the graph. -
#202 Add Frame, a titled shell with a header action, a divided tray of rows, labelled sections and a footer.
-
#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. -
#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.
-
#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. -
#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. -
#196 Add
Marquee: content that travels across its container on a loop at a speed in points per second — horizontal or vertical, edge fades, held on touch,Marquee.Groupto keep rows in step, and a still, hand-scrollable strip under reduce motion. -
#203 Add
Support: the chrome around a help conversation — availability, channels, topics, past requests, articles, and a thread's agent, ticket, handoff, suggested replies, note and resolution. -
#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. -
#217 Add PageHeader: the head of a profile or place screen — a cover that reaches under the status bar, an avatar lifted over its edge, a title block, actions and a strip of stats that compacts on screen and reads in full. Add a
2xl(96pt) Avatar size. -
#178 Add
Planner: a month of days, each showing what falls on it — category dots and icons, tinted tiles or named blocks — with swipe paging, a virtualised week scroller, a legend with counts and a bottom sheet of the selected day. -
#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. -
#209 Add
ScrollCanvas, an image in a still frame that moves as the page scrolls —parallax,zoom,reveal, or asequenceof frames scrubbed by the scroll. It follows aScreenwith no wiring or auseScrollSourcesource at any depth, takes aprogressshared value to drive it directly, and draws each effect's resting state under Reduce Motion. -
#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. -
#201 Add ScrollHeader, a collapsing large title with a pinned bar, cover and snap.
-
#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. -
#204 Add Sketch: a drawing surface with pen, shapes, eraser, colours, size and undo, drawn with Skia and exported as SVG or PNG. Needs
@shopify/react-native-skia, now an optional peer. -
#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. -
#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
-
#233 Fix
ItemandListGroup.Itemthrowing withasChildand one child. Their text wrapping always returned an array, andPressable'sasChildpath callsChildren.only, which rejects an array of one — so<Item asChild onPress={…}><View /></Item>threw. A single child now passes through as itself. -
#234
ListGroup.Itemrenders a plain view when it has neitheronPressnoronLongPress, so a static row is no longer announced as a button and runs no press feedback. Rows with a handler keep button semantics, disabled ones included. -
#235
Pressablewires screen-reader activation toonPressbehind the samedisabled/busyguard as the tap:onAccessibilityTapon iOS, so a VoiceOver double-tap no longer relies on UIKit synthesising a touch, and anactivateaction on Android, where a TalkBack double-tap previously fell through to a click with no listener. A caller that listsactivateitself still owns it, and a caller's other actions are merged in. Every control built onPressable—Button,ListGroup.Item, back and close buttons — inherits it. -
#232 Screen scrollables compose a caller's
onScrollwith their own scroll tracking.Screen.FlatList,SectionListandLegendListused to drop it;Screen.ScrollArealet it replace the tracking, freezingScrollShadowand the navbar border. A plain callback and auseAnimatedScrollHandlerresult are both accepted.
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. -
#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. -
#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.
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. -
#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. -
#131 Add the etched material, the tray and the kicker — opt-in, the default look is unchanged
SurfaceandCardtakematerial:flat(default),etched(a one-pixel highlight set into the edge, with a very soft drop) andtray(a muted frame, 4pt padding, 2xl corner, whose panels take the xl corner and the card fill).ButtonandBadgetakematerial="etched"— the primary button gains a white inset top edge, the quiet fills the light/dark edge, and an etched badge squares to the small corner with 8%/16% alpha soft status fills.InputandTextareagain anetchedvariant that rings on focus.Text.Kickeris the ten-point, 0.2em-tracked uppercase mono label.New tokens in
tokens.css:--text-kicker,--tracking-kicker,--shadow-etched,--shadow-etched-dark,--shadow-etched-primary,--shadow-focusand--shadow-focus-dark, registered withtailwind-merge.theme.cssis untouched.
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:
-
#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. -
#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. -
#139
Button.GrouptakesisAttachedandisFullWidth.isAttached={false}keeps the sharedvariant,size,isDisabledandfeedbackbut drops the joined shape: every member keeps its own corners, no seam overlaps, the run takes a gap, and a press scales again.isFullWidthspans the parent and splits it equally between members, whatever their labels say. A custom member reads both throughuseButtonGroupItem(), asisAttachedandisStretched. -
#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. -
#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. -
#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. -
#134 Add
ContextMenu— the actions that belong to a piece of content, reached by holding it. A hold opens the menu at the finger or against the whole trigger; a short press runs the trigger'sonPress, and never both.ContextMenu.Previewlifts a copy of the held content over the scrim, with the panel beside it. The rows and the panel are Menu's own, andMenu.Contentgains abackdropprop for drawing over its scrim. -
#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. -
#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. -
#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. -
#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. -
#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. -
#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. -
#133 Add
Menu— a list of actions anchored to the control that opens it. Rows run an action and close; checkbox rows stay open, radio rows close;Menu.Subexpands in place. The panel flips above the trigger near the bottom of the screen, stays inside the safe area and scrolls past the room.resolveMenuPlacementis exported for a later popover to adopt. -
#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. -
#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. -
#116
BottomSheetis rewritten on@delacour/react-native-bottom-sheet, this repository's own headless engine, with the same compound shape —Trigger,Portal,Overlay,Container,Content,ScrollView,Footer,Close,Title,Description— and eight new parts:Background,Handle,FlatList,SectionList,LegendList,TextInput,Steps/Step,HostandProvider.@gorhom/bottom-sheetis no longer imported anywhere; its peer entry leaves in the next release.Install. The engine is an optional peer, like
@delacour/react-native-charts:bun add @delacour/react-native-bottom-sheet@alphaandexpo install react-native-teleport, then rebuild the dev client — teleport is a native module and needs the new architecture.delacour add bottom-sheetdoes both.Provider.
DelacourProviderno longer mounts a sheet provider. MountBottomSheetProviderfrom@delacour/react-native-ui/bottom-sheetonce, insideDelacourProviderand around the navigator. Without it aPortalrenders where it is written, as an inline sheet.Renamed and moved props. The engine's names win, and sizing and behaviour move from
Containerto the root:<BottomSheet.Container enableDynamicSizing={false} snapPoints={…} maxDynamicContentSize={…}>→<BottomSheet dynamicSizing={false} snapPoints={…} maxDynamicContentSize={…}>.ContainertakesclassName,styleand the three*ClassNameprops only.keyboardBehavior,keyboardBlurBehavior,enablePanDownToClose,enableOverDragand the other gorhom modal props move to the root under the same names;android_keyboardInputModeis gone (keyboard-controller owns the window). New on the root:keyboardScope,detached,topInset,bottomInset(defaults to the safe-area bottom),stackBehavior,closeOnBack,keepMounted,animation,onSnapPointHaptic/onCloseHaptic/onOverDragHaptic.<BottomSheet.Overlay isCloseOnPress={false}>→<BottomSheet.Overlay pressBehavior="none">. OmittingOverlaystill draws no scrim.BottomSheet.ScrollViewno longer needsdynamicSizing={false}orsnapPoints: a list's content size is the dynamic snap point, capped bymaxDynamicContentSize.BottomSheetHandle(the ref type) is nowBottomSheetRef; the old name is a deprecated alias.useBottomSheetInput()stays as an alias of the engine'suseBottomSheetTextInput().resolveSheetBottomInset/resolveSheetScrollEndPaddingare re-exported from the engine's./core.- Removed:
BOTTOM_SHEET_KEYBOARD_DEFAULTS,resolveFooterPlacement, and the container and portal contexts.
-
#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. -
#118 Detents are snap points everywhere, matching the
snapPointsprop.DetentSpecisSnapPointSpec,onDetentHapticisonSnapPointHaptic,useBottomSheetAnimated()'sdetentsissnapPoints, the internaldetentCountissnapPointCount, and the core helpers follow (normalizeSnapPoints,parseSnapPoint,dynamicSnapPoint,crossedSnapPoint,snapPointUnder). The handle's accessibility value reads "Snap point 1 of 2". The docs page moves to/docs/bottom-sheet/snap-points. -
#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. -
#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. -
#123 Add
Swipe, a row that slides aside to reveal actions behind it.Swipe.StartandSwipe.EndholdSwipe.Actiontiles; a drag far past them fires the outermost (isFullSwipe, on by default),Swipe.Groupkeeps one row open at a time, and a ref opens or closes the row from code. Every action is also published as an accessibility action on the row. -
#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. -
#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. -
#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. -
#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.
-
#21 Add
Button.Group, which joins several controls into one segmented run — withButton.Group.Separatorfor a rule between two members andButton.Group.Textfor a chunk that says something rather than doing something. AnInputjoins the same way.<Button.Group variant="outline"> <Button onPress={archive}>Archive</Button> <Button onPress={report}>Report</Button> <Button onPress={snooze}>Snooze</Button> </Button.Group> <Button.Group> <Button onPress={save}>Save</Button> <Button.Group.Separator /> <Button accessibilityLabel="More" size="icon-md" onPress={openMenu}> <Icon icon={IconChevronDownSmall} /> </Button> </Button.Group>Each member squares the pair of corners crossing a seam and overlaps its neighbour by a point, so two adjacent borders draw as one hairline. React Native has no sibling selector, so a member's place is computed in JavaScript and published through context rather than matched with CSS — which also means a control this package has never heard of can join a run by reading
useButtonGroupItem().The group owns its members' axes: their step outright, since controls of different heights do not join, and
variant,isDisabledandfeedbackas defaults a member may override. A member keeps its own shape, so a square button still works inside a run — anicon-mdmember of ansmgroup comes outicon-sm.Nothing existing changes behaviour: a button outside a group draws exactly the corner it drew before.
- New exports:
BUTTON_GROUP_ORIENTATIONS,BUTTON_GROUP_POSITIONS,BUTTON_GROUP_SEPARATOR_ORIENTATION,BUTTON_FEEDBACK,BUTTON_GROUP_FEEDBACK,useButtonGroup,useButtonGroupContext,useButtonGroupItem,useButtonGroupItemContext,ButtonGroupProvider,ButtonGroupItemProvider, and the pure resolversresolveGroupPositions,resolveGroupSeams,resolveButtonFeedback,resolveGroupedButtonSizeandresolveButtonSizeStep.
- New exports:
-
#53 Name both libraries for what they are
@delacour/react-native-uiand@delacour/react-native-charts— the names they published under before the scope, with thedelacour-prefix traded for@delacour/rather than dropped. Every subpath keeps its spelling (@delacour/react-native-ui/button,@delacour/react-native-charts/core), so only the package half of an import changes.The pair now matches: the charts package was briefly
@delacour/charts, which did not say React Native and did not sit beside its sibling. Neither intermediate spelling reached npm.The CLI stays
delacour, unscoped, because it is the thing people type:bunx delacour@alpha add button.@types/reactis pinned in the workspace catalog as part of this: four packages declared three different ranges, so two copies were always installed and only hoisting order decided which reached the root. The rename changed that order, splitReftypes across two copies and collapsed everyComponentRef<typeof Animated.View>tonever. -
#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. -
#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. -
#19 Breaking.
Button'sisIconOnlyprop is removed. A square footprint is now a size:size="icon-sm",size="icon-md"orsize="icon-lg".// before <Button accessibilityLabel="Favourite" isIconOnly size="sm" variant="ghost"> // after <Button accessibilityLabel="Favourite" size="icon-sm" variant="ghost">Each
icon-*size is its labelled step with the horizontal padding traded for a width off the same token, which is shadcn's spelling and makes padding and width mutually exclusive by construction rather than by rule.BUTTON_SIZESnow holds all six values, and is derived from the newBUTTON_LABEL_SIZESandBUTTON_ICON_SIZEStuples.ButtonSizewidens to match;ButtonLabelSizeandButtonIconSizeare exported alongside it.buttonVariantsno longer acceptsisIconOnly; pass the size instead. Its signature now matches the public prop exactly, sobuttonVariants({ size: "icon-md" })works.ButtonLayoutlosesisIconOnly, andresolveButtonLayoutno longer takes it — it now folds onlyisLoadingandspinnerPlacement.useButton()reportssizeverbatim, so an icon button returns"icon-lg"rather than"lg". Match againstBUTTON_ICON_SIZESwhere a child needs to know it sits inside a square one.
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. -
#112
Chip,BadgeandAlerttake acloseTestIDand forward it to the close control they compose in, so a test or automation can press "Remove" or "Dismiss" by id rather than by its label. -
#103 Pressable multiplies a className's opacity into its press feedback instead of overwriting it, so a disabled Button, Badge, Checkbox, ListGroup.Item or Input.Group now fades as its variant asks.
-
#62 End
BottomSheet.ScrollViewat a sticky footer's top edge, so the scroll indicator and the overscroll bounce stop above the footer instead of running on underneath it -
#100
Slider.Thumbnow has an accessible name. It defaults to the enclosingField.Label's text — the label alone for one thumb, "…, minimum" and "…, maximum" for a range's two — and anaccessibilityLabelon the thumb wins outright. A range with no label still reads "Minimum" and "Maximum".Field.Labelregisters its text with the field's context (label/registerLabel), so any control can read it;resolveFieldLabelTextandresolveThumbAccessibilityLabelare exported. -
#46 Fix the corner seam on a checked
CheckboxThe fill sat exactly inside the border, so its outer curve and the border's inner curve were the same curve rasterised on two layers. Antialiased independently they under-cover where they meet, letting the box's own
bg-cardbleed through as a dull arc at each corner — on iOS and Android alike, and at every size. The straight edges are pixel-aligned, which is why only the corners showed it.The fill now overlaps the border ring instead of meeting it:
-inset-pxreaches past the padding box, and it wears the box's own corner rather than one a border width tighter. There is no longer a shared edge to leave a seam.
0.1.0-alpha.5
Patch changes
-
#103 Pressable multiplies a className's opacity into its press feedback instead of overwriting it, so a disabled Button, Badge, Checkbox, ListGroup.Item or Input.Group now fades as its variant asks.
-
#100
Slider.Thumbnow has an accessible name. It defaults to the enclosingField.Label's text — the label alone for one thumb, "…, minimum" and "…, maximum" for a range's two — and anaccessibilityLabelon the thumb wins outright. A range with no label still reads "Minimum" and "Maximum".Field.Labelregisters its text with the field's context (label/registerLabel), so any control can read it;resolveFieldLabelTextandresolveThumbAccessibilityLabelare exported.
0.1.0-alpha.4
Patch changes
- #62 End
BottomSheet.ScrollViewat a sticky footer's top edge, so the scroll indicator and the overscroll bounce stop above the footer instead of running on underneath it
0.1.0-alpha.3
Minor changes
-
#53 Name both libraries for what they are
@delacour/react-native-uiand@delacour/react-native-charts— the names they published under before the scope, with thedelacour-prefix traded for@delacour/rather than dropped. Every subpath keeps its spelling (@delacour/react-native-ui/button,@delacour/react-native-charts/core), so only the package half of an import changes.The pair now matches: the charts package was briefly
@delacour/charts, which did not say React Native and did not sit beside its sibling. Neither intermediate spelling reached npm.The CLI stays
delacour, unscoped, because it is the thing people type:bunx delacour@alpha add button.@types/reactis pinned in the workspace catalog as part of this: four packages declared three different ranges, so two copies were always installed and only hoisting order decided which reached the root. The rename changed that order, splitReftypes across two copies and collapsed everyComponentRef<typeof Animated.View>tonever.
0.1.0-alpha.2
Minor 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.
Patch changes
-
#46 Fix the corner seam on a checked
CheckboxThe fill sat exactly inside the border, so its outer curve and the border's inner curve were the same curve rasterised on two layers. Antialiased independently they under-cover where they meet, letting the box's own
bg-cardbleed through as a dull arc at each corner — on iOS and Android alike, and at every size. The straight edges are pixel-aligned, which is why only the corners showed it.The fill now overlaps the border ring instead of meeting it:
-inset-pxreaches past the padding box, and it wears the box's own corner rather than one a border width tighter. There is no longer a shared edge to leave a seam.
0.1.0-alpha.1
Minor 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.
-
#21 Add
Button.Group, which joins several controls into one segmented run — withButton.Group.Separatorfor a rule between two members andButton.Group.Textfor a chunk that says something rather than doing something. AnInputjoins the same way.<Button.Group variant="outline"> <Button onPress={archive}>Archive</Button> <Button onPress={report}>Report</Button> <Button onPress={snooze}>Snooze</Button> </Button.Group> <Button.Group> <Button onPress={save}>Save</Button> <Button.Group.Separator /> <Button accessibilityLabel="More" size="icon-md" onPress={openMenu}> <Icon icon={IconChevronDownSmall} /> </Button> </Button.Group>Each member squares the pair of corners crossing a seam and overlaps its neighbour by a point, so two adjacent borders draw as one hairline. React Native has no sibling selector, so a member's place is computed in JavaScript and published through context rather than matched with CSS — which also means a control this package has never heard of can join a run by reading
useButtonGroupItem().The group owns its members' axes: their step outright, since controls of different heights do not join, and
variant,isDisabledandfeedbackas defaults a member may override. A member keeps its own shape, so a square button still works inside a run — anicon-mdmember of ansmgroup comes outicon-sm.Nothing existing changes behaviour: a button outside a group draws exactly the corner it drew before.
- New exports:
BUTTON_GROUP_ORIENTATIONS,BUTTON_GROUP_POSITIONS,BUTTON_GROUP_SEPARATOR_ORIENTATION,BUTTON_FEEDBACK,BUTTON_GROUP_FEEDBACK,useButtonGroup,useButtonGroupContext,useButtonGroupItem,useButtonGroupItemContext,ButtonGroupProvider,ButtonGroupItemProvider, and the pure resolversresolveGroupPositions,resolveGroupSeams,resolveButtonFeedback,resolveGroupedButtonSizeandresolveButtonSizeStep.
- New exports:
-
#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. -
#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. -
#19 Breaking.
Button'sisIconOnlyprop is removed. A square footprint is now a size:size="icon-sm",size="icon-md"orsize="icon-lg".// before <Button accessibilityLabel="Favourite" isIconOnly size="sm" variant="ghost"> // after <Button accessibilityLabel="Favourite" size="icon-sm" variant="ghost">Each
icon-*size is its labelled step with the horizontal padding traded for a width off the same token, which is shadcn's spelling and makes padding and width mutually exclusive by construction rather than by rule.BUTTON_SIZESnow holds all six values, and is derived from the newBUTTON_LABEL_SIZESandBUTTON_ICON_SIZEStuples.ButtonSizewidens to match;ButtonLabelSizeandButtonIconSizeare exported alongside it.buttonVariantsno longer acceptsisIconOnly; pass the size instead. Its signature now matches the public prop exactly, sobuttonVariants({ size: "icon-md" })works.ButtonLayoutlosesisIconOnly, andresolveButtonLayoutno longer takes it — it now folds onlyisLoadingandspinnerPlacement.useButton()reportssizeverbatim, so an icon button returns"icon-lg"rather than"lg". Match againstBUTTON_ICON_SIZESwhere a child needs to know it sits inside a square one.