@rocapine/react-native-onboarding-ui
Changelog
All notable changes to @rocapine/react-native-onboarding-ui are documented
here.
[Unreleased]
[1.50.1] - 2026-06-19
Fixed
- ComposableScreen
RadioGroup/CheckboxGroup— tick not pinned to edge withtickPosition: "end". The item row had nojustifyContent, so the label and tick clumped together on the left instead of the tick sitting at the right edge of the full-width card. Items now applyjustifyContent: "space-between"whentickPosition === "end", distributing the label to the left and the tick to the right edge.tickPosition: "start"(default) is unchanged.
[1.50.0] - 2026-06-19
Added
- ComposableScreen
RadioGroup/CheckboxGrouprenderers — tick + sub-label customization. Tick placement honorstickPosition("start"/"end"); tick color/radius/size come fromtickColor/tickSelectedColor/tickBorderRadius/tickSizeper selection state (tickSizedefault20— radio's inner dot and checkbox's ✓ glyph fontSize/lineHeight scale with it; radiotickBorderRadiusdefaults totickSize / 2). Items render an optionalsubLabelline (own font + color resolved once viauseResolvedFontStyle, state-aware viaitemSubLabel*/itemSelectedSubLabelColor). Itemlabelis optional; the tick↔text and label↔sub-label gaps collapse when a line is absent. Accessibility label falls backlabel → subLabel → value.
[1.49.1] - 2026-06-19
Fixed
- ComposableScreen Carousel active dot sizing —
activeDotWidth/activeDotHeighthad no visual effect because the renderer usedPagination.Basic, which sizes every dot fromdotStyle(clipped viaoverflow: hidden) and never appliesactiveDotStylewidth/height (active resizing is an unimplemented TODO inreact-native-reanimated-carousel). Switched toPagination.Custom, which interpolates width/height/borderRadius/backgroundColor between active and inactive dots, so active dot sizing now renders.
[1.49.0] - 2026-06-19
Added
- ComposableScreen
Carouselelement dots now support active-dot sizing and placement. The renderer appliesactiveDotWidth/activeDotHeighttoPagination.Basic's active dot, renders the dot row above or below the carousel viadotsPosition, and honorsdotsMarginBottom. Defaults preserve the prior look (active dot = inactive size, dots below, no bottom margin).
[1.48.0] - 2026-06-19
Added
- Carousel pagination dots are now customizable — the
Carouselrenderer readspayload.paginationto control dot colors, inactive/active width & height, gap, vertical placement (position: "top" | "bottom"), and top/bottom margins, and can hide the dots entirely (show: false). Defaults reproduce the previous hardcoded styling.
[1.47.0] - 2026-06-19
Changed
ProgressBarno longer importsexpo-router— its back button now uses the navigation adapter fromuseOnboardingNavigation()(canGoBack()/goBack()).expo-routeris now an optional peer dependency; existing expo-router apps keep the same behavior with no changes, and other navigation libraries work by injecting anavigationadapter intoOnboardingProvider.
[1.46.0] - 2026-06-18
Added
DrawingPadComposableScreen element renderer — a freehand drawing / signature canvas. Captures multi-stroke input viareact-native-gesture-handlerand Skia paths; on each completed stroke it serializes the drawing into the bound variable(s): an SVG path string (variableName) viapath.toSVGString()and/or a base64 image data URI (imageVariableName) rendered off an offscreen Skia surface. SupportsstrokeColor,strokeWidth,backgroundColor,clearable,imageFormat, a fully customizable clear button (clearButtonPosition(top/bottom × left/right),clearButtonOffset,clearButtonSize,clearButtonColor,clearButtonIconColor,clearButtonLabel), and allBaseBoxProps. Requires the optional peer dependency@shopify/react-native-skia(throws an explicit install error when absent). Wired intorenderElementand added toPRESS_HANDLED_TYPES(owns its own gesture).
[1.45.0] - 2026-06-18
Added
Sliderelement renderer — renders a continuous numeric slider that reads/seeds/writes its bound variable as a float. Backed by the new optional peer dep@react-native-community/slider; degrades to an empty box when the dep is absent (mirrorsGradientBox's silent fallback). Track/thumb tints default to the themeprimary/neutral.low. Wired intorenderElement(dispatch +PRESS_HANDLED_TYPES, since it owns its gesture) andcollectElementDefaults(first-render default seed).
[1.44.7] - 2026-06-18
Fixed
backgroundGradientonButton(and other elements) no longer blows the element up to fill the screen. The gradient render path nested the content inside<GradientBox style={{ flex: 1 }}>with an innerflex: 1view, while the non-gradient path was content-sized. In aZStack/flex container thatflex: 1grabbed the parent's full main-axis, so a gradientButton(orSafeAreaView/KeyboardAvoidingView/ScrollView) expanded to the whole screen. The innerflex: 1is now gated behind an explicitheight/flex/flexGrow, so a content-sized element stays content-sized with or without a gradient. Affected renderers:ButtonElement,SafeAreaViewElement,KeyboardAvoidingViewElement,ScrollViewElement.
[1.44.6] - 2026-06-18
Changed
- Version sync with
@rocapine/react-native-onboarding@1.44.6(asset prefetch/preload now works forComposableScreensteps). No UI changes.
[1.44.5] - 2026-06-16
Fixed
- Staggered autoplay
ProgressIndicatorloader bars no longer reset to empty. When severalautoplaylinear bars ran on one screen (e.g. a "curating your profile…" loader), bars that finished early painted empty while only the last-finishing bar stayed filled — even though every bar's bound variable correctly reached its max (sorenderWhen: eq maxcheckmarks stayed visible, exposing the desync). Cause: each autoplay bar wrote its bound variable on every animation step (~20×/s), and everysetVariablere-rendered all ComposableScreen variable consumers; on Fabric / Reanimated 4 that re-render storm reverted the already-settled animated fill of sibling bars. Fixes:- Autoplay bars now write the bound variable only at the sweep boundaries
(start / completion) instead of on every step, eliminating the re-render
storm. The live numeric
%is still rendered natively viashowLabel. (A consumer interpolating the variable mid-sweep with{{var}}now sees it jump min→max — useshowLabelfor a live readout.) - The linear fill is driven by a left-anchored
scaleXtransform instead of an animated percentagewidth, which commits reliably on Fabric. - Autoplay progress is seeded from the bound variable on mount, so a completed bar is restored to full if the screen subtree remounts.
- Added dependency arrays to the animated worklets to avoid mapper churn.
- Autoplay bars now write the bound variable only at the sweep boundaries
(start / completion) instead of on every step, eliminating the re-render
storm. The live numeric
[1.44.4] - 2026-06-16
Fixed
- Empty / null
fontFamilynow falls back to the theme default. A text element (Text,Button,Input,RadioGroup,CheckboxGroup,WheelPicker,AnimatedText, rich-text spans) that provided no usable font only fell back totheme.typography.defaultFontFamilywhenfontFamilywasundefinedor"inherit". The CMS emits an empty string ("") ornullfor "no font selected", which slipped throughresolveInheritedFontFamilyunchanged — a falsy family then reachedresolveFontFamily, which returnsundefined(system font) and silently ignored the configured default.resolveInheritedFontFamilynow treats any falsy value (""/null/undefined) as well as"inherit"as "use the theme default". fontStylenow resolves the italic face onButton/Input/RadioGroup/CheckboxGroup. These passed onlyfontFamily+fontWeighttouseResolvedFontStyle, so a registered italic variant (e.g.PlayfairDisplay-Italic) was never selected — text fell back to synthetic italic over the upright face.fontStyleis now threaded into resolution so the real italic face is picked when registered (matchingText/AnimatedText).
[1.44.3] - 2026-06-16
Changed
- Version sync with
@rocapine/react-native-onboarding@1.44.3(production fallback-cache fix in the headless SDK). No UI/renderer changes.
[1.44.2] - 2026-06-15
Fixed
- Italic text renders with the italic face.
TextElement(incl. rich-text spans) andAnimatedTextElementnow passfontStyleintouseResolvedFontStyle, so an italic request resolves to the registered italic font face instead of the upright one. Paired with@rocapine/react-native-onboarding1.44.2.
[1.44.1] - 2026-06-15
Changed
- Version bump only — paired with
@rocapine/react-native-onboarding1.44.1 (runtime fonts register under their PostScript / file name). No UI changes.
[1.44.0] - 2026-06-11
Added
OnboardingPagekeyboardVerticalOffset— optional number forwarded to theComposableScreenrenderer'sKeyboardAvoidingView(default0). Hosts that renderOnboardingPagebelow a fixed header (e.g. apaddingTop: HEADER_HEIGHTwrapper whendisplayProgressHeaderis true) push the view's top down, so the iOSbehavior="padding"math under-compensates by exactly that offset and the bottom CTA stays hidden behind the keyboard on steps containing anInput. Pass the header height (keyboardVerticalOffset={HEADER_HEIGHT}) to compensate. Other step renderers are unchanged.
[1.43.0] - 2026-06-11
Added
ProgressiveBlurImageblurAppear— fades the masked-blur + tint layer in over the always-visible sharp base image after an optional delay, via a reanimated opacity wrapper (withDelay+withTiming, reusing the sharedEASING_MAP).{ delay? (ms, default 0), duration? (ms, default 400), easing? (default "ease-out") }. Omitting it renders the blur statically at full strength on mount (unchanged). The degraded scrim fallback is unaffected.
[1.42.1] - 2026-06-11
Fixed
- Button
flexignored —ButtonElementnow forwardsflex/flexShrink/flexGrowfrom its resolved props in both render branches (gradient + default outerAnimated.View). Previously theseBaseBoxPropsfields were dropped, so aButtonwithflex: 1always sized to its content; equal-width / proportional buttons inside anXStacknow work without wrapping each Button in aflex: 1container. ThealignSelfdefault ("stretch"when nowidth) is unchanged, so content-sized buttons behave as before.
[1.42.0] - 2026-06-10
Added
- RadioGroup / CheckboxGroup per-item shadow — item rows now honor
itemShadowColor/itemShadowOffset/itemShadowOpacity/itemShadowRadius/itemElevationviabuildShadowStyleon eachTouchableOpacity. Items carry nooverflow: hidden, so the iOS shadow is not clipped; a loneitemShadowColordefaults opacity to1and radius to4.
[1.41.2] - 2026-06-10
Fixed
shadow*props now render onXStack/YStack/ZStackcontainers —buildShadowStylewas only wired intoButtonElementandImageElement, soshadowColor/shadowOffset/shadowOpacity/shadowRadius/elevationset on Stack containers were silently dropped.StackElementandZStackElementnow spreadbuildShadowStyle(p)into their style objects. (iOS shadows still requireoverflow≠hiddenon the shadowed view.)
[1.41.1] - 2026-06-09
Fixed
- Static
transformnow applies from frame 0 when an element also has an entering animation — reanimated'sentering/exiting/layoutbuilders take over the host view's transform for the duration of the transition, so a statictransform(or continuouseffect) placed on the sameAnimatedBoxview was suppressed until the entry finished, then snapped in.AnimatedBoxnow nests the two onto separate views when a reanimated builder is present: the outer (parent-facing) view keepsflex/alignSelf+ the builder, the inner view carries the static transform/effect — so they stack instead of fighting. No-builder elements (transform/effect only) keep the single-view fast path.
[1.41.0] - 2026-06-09
Added
autoFocusprop onInputelement — whentrue, theTextInputfocuses on mount and the keyboard opens automatically. Optional, defaults tofalse.
[1.40.0] - 2026-06-09
Changed
- Version sync only — no UI changes. Bumped in lockstep with
@rocapine/react-native-onboarding1.40.0 (headless background asset preloader that warms remote image/video/Lottie/Rive/SVG assets from the payload after fetch). UI renderers are unchanged; preloaded assets are served from cache when each screen mounts.
[1.39.0] - 2026-06-08
Added
AnimatedTextUIElement — a number that count-animatesfrom→toand renders as formatted text (decimals,thousandsSeparator,easing,loop). The animation runs entirely on the UI thread and writes straight into a nativeTextInputviauseAnimatedProps({ text })(the react-native-redashReTextpattern), so it produces zero React re-renders per frame and never writes a composable variable. It is the performant replacement for driving a count-up through anautoplayProgressIndicatorbound to a variable (which re-renders the whole ComposableScreen tree on every step). Renders the number only — compose static labels as siblingText.
Changed
ProgressIndicatorshowLabelno longer re-renders — the label was React state (useState+runOnJS(setDisplayValue)per step hop), so ashowLabelindicator re-rendered itself on every step and churned the reanimated mapper scheduler (visibly destabilizing other on-screen animations). The label is now a nativeTextInputdriven from a worklet (same technique asAnimatedText), soshowLabeladds zero re-renders. ThesetVariablewrite for a boundvariableNameis unchanged (still the documented per-step write — keepstepcoarse for large ranges, or useAnimatedTextfor pure display).
[1.38.2] - 2026-06-08
Fixed
- Entry transitions restarting on re-render —
AnimatedBoxrebuilt itsentering/exiting/layoutreanimated builders inline on every render, handingAnimated.Viewa freshenteringinstance each time and re-firing the entry transition. With an autoplayProgressIndicatoron screen (writes its bound variable each step → re-renders the whole ComposableScreen tree), every sibling's entry animation visibly reloaded. The builders are now memoized on their (stable, from the memoized parsed step) spec objects.
[1.38.1] - 2026-06-08
Fixed
- Loader
CircularProgressper-frame re-render — the percentageuseAnimatedReactionrounded inside its JS callback, firingsetPercentageevery frame (~60×/s) and re-rendering the component continuously; it also had no deps array, so Reanimated rebuilt the mapper on every render (resettingprev). Now rounds inside the reader with aprevguard and a[]deps array, so the JS callback fires only when the displayed integer changes. - Loader
StepProgresslistener thrash — theprogress.addListenereffect was keyed onbarStarted/barComplete, the very states its callback flips, so eachsetStatetore the listener down and re-attached it mid-animation. The one-time start/complete transitions now live in refs and the effect deps are[progress](attaches once).
Changed
- ComposableScreen flattens variables once per render —
renderElementrebuiltflatVarsviaObject.fromEntriesfor every element on every tree re-render; an autoplayProgressIndicatorwriting a variable each step re-renders the whole tree, making this pure churn. The flatten is now memoized once inRendererasctx.flatVariables(added toRenderContext) and reused byrenderElement,RichTextElement, andButtonElement.
[1.38.0] - 2026-06-08
Added
ProgressIndicatorarbitrary value range — the renderer decouples the fill fraction (always 0–1, derived as(value − minValue) / (maxValue − minValue)) from the displayed value (in[minValue, maxValue]).autoplayanimates tomaxValue; the label and theautoplay-written variable now carry the raw value snapped tostep, withlabelSuffix(default"%") appended. Lets aProgressIndicatordrive an animated count-up to N (read via{{var}}in aText). TheuseAnimatedReactionworklet keys on the step-snapped value (not the rounded percent) and re-keys onminValue/maxValue/step, so the JS callback fires(maxValue − minValue) / steptimes per sweep — coarsestepavoids a per-step re-render storm on large ranges.
Changed
ProgressIndicatorlabel is no longer percent-only — both label render sites show{value}{labelSuffix}instead of a hardcoded{percent}%; the internalclampis now range-aware (clamp(n, min, max)). With default props (minValue:0,maxValue:100,step:1,labelSuffix:"%") the rendered output is unchanged.
[1.37.0] - 2026-06-08
Added
- Generic
onPresson non-pressable elements —renderElementnow wraps any element declaringonPress: ButtonAction[]in a single centralPressable(mirroring the existingAnimatedBoxwrapper), dispatching the same action list asButtonvia a new sharedrunActionshelper. Makes static elements (Text, Icon, Image, Lottie, Rive, Video, ProgressIndicator, RichText, Stacks, ZStack, SafeAreaView, ScrollView, KeyboardAvoidingView, Carousel) tappable. Skipped for elements that own their own tap/focus/scroll gesture (Button,RadioGroup,CheckboxGroup,DatePicker,Input,WheelPicker). ThePressableis layout-transparent — it forwards the element'sflex/flexGrow/flexShrink(incl. theparentType === "XStack"shrink default) /alignSelf, so a tappable element still splits/flows in its parent's flex context exactly as it would un-wrapped (e.g. flex:1 cards in a row grid). arrayOpmulti-select support inrunActions— asetVariableaction witharrayOp: "append" | "remove" | "toggle"reads the target variable's JSON-encodedstring[](theCheckboxGroupencoding), applies the set operation tovalue, and re-storesJSON.stringify(values)+ comma-joined member labels.appenddedups,toggleflips,removedrops; the label list stays aligned to the value list. Makes a tappable card behave like a checkbox.
Changed
- Extracted
runActionsfromButtonElement— the press-action dispatch loop (continue / setVariable / custom) moved intoelements/runActions.tsand is now shared byButtonand the genericonPress.Button's behavior (haptic,disabledWhen,pressedStyle) is unchanged.ButtonActiontypes/schemas moved toelements/actions.ts(re-exported fromButtonElementfor back-compat).
[1.36.2] - 2026-06-08
Fixed
- Theme font now applies to all ComposableScreen text elements —
RadioGroup/CheckboxGroupitem labels,WheelPickeritems, and the AndroidDatePickertrigger label previously rendered in the system font when theirfontFamily/itemFontFamilyprop was omitted, ignoringtheme.typography.defaultFontFamily. They now resolve throughresolveInheritedFontFamily+ the font registry (matchingButton/Text/Input), so omitted font falls back to the theme default and weighted variants are matched correctly (synthetic bold suppressed viaresolvedToVariant).
[1.36.1] - 2026-06-04
Fixed
ProgressiveBlurImageelement on React Native 0.85 — replaced removedStyleSheet.absoluteFillObjectwithStyleSheet.absoluteFill(RN 0.85 dropped the former; the latter is now the equivalent frozen style object). Fixes the build under Expo SDK 56.
Changed
- Expo SDK 56 / React Native 0.85 alignment — bumped build-time dev dependencies (
react19.2.3,react-native0.85.3,expo-router~56.2.8,expo-store-review~56.0.3,react-native-gesture-handler~2.31.1,react-native-reanimated4.3.1,react-native-safe-area-context~5.7.0,react-native-svg15.15.4,@react-native-community/datetimepicker^9.1.0).react-native-svg15.15.4 fixes a native build break against RN 0.85'sImageResponseObserversignature. No runtime/API changes (peer deps stay*).
[1.36.0] - 2026-06-04
Added
- Uniform image blur — the
ImageComposableScreen renderer now forwards ablurRadiusprop to bothexpo-imageand RNImage(native blur, no extra dep).0/omitted = sharp; ignored for SVGs. ProgressiveBlurImageelement renderer — renders a full-bleed sharp image with a gradient-masked blurred copy of the same image on top (revealed where themaskis opaque) plus an optionaltintgradient, producing a progressive (variable) blur: sharp where the mask is transparent, blurred + tinted where it's opaque. Masking a blurred image copy (rather than a backdropBlurView) is what makes it composite reliably on iOS — a maskedBlurViewhas no backdrop to sample and renders transparent. Supports both linear and radial masks: linear renders viaexpo-linear-gradient, radial viareact-native-svg(a required dep — radial works even without expo-linear-gradient). The tint overlay + degraded scrim follow the same mask shape. Composes as the bottom layer of aZStackwith sharp foreground content above. A native-view probe + error boundary degrade to a sharp image + dark scrim (never throws) when the masked-view native module isn't in the running binary.
Changed
@react-native-masked-view/masked-viewadded as an optional peer dependency — needed (alongside the existingexpo-linear-gradientfor the mask/tint gradients andexpo-imagefor the blurred copy) byProgressiveBlurImage. When absent the element degrades gracefully to a sharp image + a dark gradient scrim derived from the mask (still legible for overlaid text). Themaskis linear-only; a radial source mask is approximated by a vertical fade.
[1.35.0] - 2026-06-02
Added
- Haptic feedback on clickable ComposableScreen elements —
Button,RadioGroup, andCheckboxGrouprenderers fire tactile feedback on press / select / toggle when their newhapticprop is set ("light" | "medium" | "heavy" | "soft" | "rigid";"none"or omitted = silent). Powered by a sharedtriggerHaptichelper (elements/haptics.ts) that dynamically requires the new optionalexpo-hapticspeer dependency — silently no-ops when the dep isn't installed, mirroring theexpo-store-review/expo-linear-gradientpattern.
Changed
expo-hapticsadded as an optional peer dependency — install only if you opt into thehapticprop.
[1.34.1] - 2026-06-02
Fixed
ProgressIndicatorresetting after it finishes —useAnimatedReactionwas created without a dependency array, so reanimated 4 tore down and rebuilt the mapper on every render. A loopingshowLabelindicator re-renders ~40×/s indefinitely (onesetPercentageper frame), churningstartMapper/stopMapperon the UI-thread scheduler and destabilizing other running animations on the same screen — the "autoplay once" indicator would occasionally snap back to its initial value after completing. The reaction is now keyed on[showLabel, writesVariable, variableName]so the mapper stays stable across renders (this also keepsprevalive, restoring therounded === prevover-fire guard).
[1.34.0] - 2026-06-02
Added
- WebP / AVIF image support — the
Imageelement now renders viaexpo-imagewhen installed (new optional peer dep), falling back to React Native'sImagewhen absent (same try/require pattern asGradientBox/expo-linear-gradient). RN's built-inImageis unreliable for WebP on iOS;expo-imagedecodes WebP/AVIF reliably cross-platform.resizeModemaps to expo-imagecontentFit(cover/containpass through,stretch→fill,center→none). - SVG image support — the
Imageelement auto-detects URLs whose path ends in.svg(query-string / hash tolerant) and renders them withreact-native-svg'sSvgUri(already a dependency). No schema change — existing payloads with.svgURLs just work.resizeModemaps to SVGpreserveAspectRatio(cover→xMidYMid slice,contain/center→xMidYMid meet,stretch→none). ScrollViewelementalignItems/justifyContent— renders the new optionalScrollViewprops (see headless1.34.0) on the scroll content container for cross-axis alignment + distribution along the scroll axis.
Fixed
- Horizontal
ScrollViewno longer "stuck" / unscrollable — children of a horizontalScrollViewwere rendered withparentType"XStack", which applied aflexShrink: 1default, so fixed-width cards shrank to fit the viewport instead of overflowing (the row couldn't scroll). Horizontal scroll content now renders with a dedicated"XScroll"parentType(row layout, noflexShrinkdefault) and dropsflexGrow: 1from its content container, so children keep their intrinsic width and the row scrolls. (VerticalScrollViewkeepsflexGrow: 1so a short payload still fills the viewport.) RichTexttextAlignnow aligns the wrapping row —textAlignwas published to childTextelements viaRichTextStyleContextbut had no visible effect on the row itself (each word is a shrink-wrapped flex item, sotextAlignis a no-op there); the row's horizontal distribution is governed byjustifyContent, which defaulted to"center".textAlignnow maps onto the row'sjustifyContentwhenjustifyContentisn't set explicitly (left→flex-start,center→center,right→flex-end).
[1.33.0] - 2026-06-01
Added
RichTextcontainer renderer — renders the newRichTextUIElement as a wrapping flex row (<View>/GradientBox,flexDirection:"row",flexWrapdefault"wrap"). Children (Textelements) render throughrenderElementas real flex children, so each honors its own box props (padding,borderRadius,border,backgroundColor,margin,transform) — enabling padded/rounded/rotated chip segments — plusrenderWhen/expression. Supportsgap,alignItems(incl."baseline"), andjustifyContent. Unlike inlineTextSpans,RichTextchildren may useanimation/transform(theAnimatedBoxViewwrapper is valid inside the row). The container's text-style props (fontSize,color,textAlign, …) are published via a newRichTextStyleContextand merged byTextElementComponentas inherited defaults (child props win) — so a title's base typography is declared once on the container. Plain-text children are expanded into one inlineTextper word (spaces preserved) so the row wraps word-by-word; children with box styling or motion stay atomic chips. (Because spaces become real flex items, avoidgapwhen mixing words + chips — use chipmarginHorizontal.)
[1.32.0] - 2026-06-01
Added
AnimatedBoxwrapper +buildAnimationhelper — renders the newtransform/animationsurface (see headless1.32.0) for every ComposableScreen element.renderElementwraps the dispatched node in a singleAnimated.View(AnimatedBox) only whenanimationortransformis present (zero extra view otherwise), forwardingflex/alignSelfso the wrapper stays layout-transparent.entering/exiting/layoutresolve to reanimated builders by name (Reanimated[preset]) with.duration().delay().springify().easing()modifiers; unknown presets degrade to no-op. Continuouseffect(pulse/fade/rotate/shimmer/bounce) runs imperatively viawithRepeat. No new peer deps — uses the existingreact-native-reanimatedstack.- Shared
EASING_MAPextracted tobuildAnimation.ts;ProgressIndicatorElementnow imports it (removes the duplicated easing table). - New
composable-screen-animationsexample screen (entering presets staggered bydelay, spring vs easing, looping effects, static transforms, exiting + layout toggle, Replay button).composable-screen.tsx+onboarding-example.tsdemos: hero image fades in (FadeInDown), star icon zooms in with a static tilt and a continuouspulse. RichTextSpanextended — applies the newTextSpanfields (backgroundColor,opacity,textTransform,textDecorationColor,textDecorationStyle,lineHeight) to the nested inline<Text>.
[1.31.0] - 2026-06-01
Added
- Inline rich-text rendering in
TextElement— whencontentis a span array, the renderer maps each span to a nested<Text>(new internalRichTextSpancomponent) so fragments with different weight/style/color/decoration wrap together on one baseline. Each span resolves its own font viauseResolvedFontStyleagainst the parentText's inherited family, so a span setting onlyfontWeightstill picks the correct weighted font variant. Supports per-spanfontWeight,fontStyle,fontFamily,fontSize,letterSpacing,color,textDecorationLine.
Changed
TextElementPropsSchema.contentmirror widened tostring | TextSpan[];TextSpan/TextSpanSchemaadded to the UI element. Plain stringcontentrenders identically to before. Expression mode interpolates{{variable}}inside each span'stext.
[1.30.0] - 2026-05-29
Added
ProgressIndicatorElementrenderer — renders theProgressIndicatorUIElement in both variants. Linear uses an animated track-fillView; circular uses an animatedreact-native-svgring (both driven byreact-native-reanimated— no new peer deps; same stack asCircularProgress).easingnames map to CSS cubic-bezier curves (linear,ease-in(0.42,0,1,1),ease-out(0,0,0.58,1),ease-in-out(0.42,0,0.58,1)).autoplayanimatesinitialValue → 100(optionallylooping, optionally after adelayms viawithDelay) and writes the rounded value tovariableNameon each integer-percent change (reaction keyed on the rounded value, not per-frame, to avoid a context re-render storm); withoutautoplaythe indicator animates toward the bound variable / staticvalue. OptionalshowLabelrenders the live percentage.composable-screen.tsx+onboarding-example.tsdemos exercise a linear autoplay-loop and a circular autoplay-once indicator.
[1.29.0] - 2026-05-29
Added
DatePickerElement:"now"sentinel support — renderer mirrors the headless schema and resolvesdefaultValue/minimumDate/maximumDatevia aresolveDatehelper that maps the literal"now"tonew Date()at render time (ISO strings still parse as before). Initial value,minimumDate, andmaximumDatepassed to the native picker all honor"now".composable-screen.tsx+onboarding-example.tsdemos now usemaximumDate: "now".
[1.28.0] - 2026-05-29
Added
RadioGroupElement/CheckboxGroupElement:showTicksupport — both renderers mirror the headlessshowTickfield and gate the indicator onshowTick !== false. WithshowTick: falsethe radio circle / checkbox✓box is not rendered, leaving label + selected background/border to convey state; default (true/ omitted) is unchanged.composable-screen.tsx+onboarding-example.tsdemos exercise both states (radio shows the tick, checkbox hides it).
[1.27.0] - 2026-05-29
Added
WheelPickerelement renderer — renders the newWheelPickerUIElement using the optional@react-native-picker/pickerpeer dep (native iOS wheel / Android dropdown). Seeds + writes its bound variable likeRadioGroup(full{value, label}entry), resolvesitems/rangevia the shared headlessresolveWheelPickerItemshelper, and contributes tocollectElementDefaultssodefaultValueis visible torenderWhen/{{var}}on first render. Falls back to a clear placeholder when the peer dep is absent.
[1.26.0] - 2026-05-28
Added
IconElementfilled / tinted rendering —IconElement.tsxnow mirrors headlessfill+fillOpacityschema fields and passes them through to the underlyinglucide-react-nativeSVG (extendsreact-native-svg'sSvgProps). Authors can render filled lucide icons or tinted overlays directly from CMS payload, e.g.{ "fill": "#007AFF", "fillOpacity": 0.25 }. Default behaviour unchanged — omitfilland icons render outlined as before.
[1.25.1] - 2026-05-28
Added
aspectRatioon every UIElement (viaBaseBoxProps) — wired into the Rive renderer's wrapper; other element renderers can opt-in by readingp.aspectRatio.
Changed
-
ComposableScreen page no longer wraps content in a
ScrollView— the wrapper container'sflexGrow: 1left innerflex: 1children unbounded vertically, so aCarousel(or anyflex: 1element) grew with its intrinsic content and pushed siblings off-screen. Payloads needing scroll should use theScrollViewUIElement (added in 1.25.0).KeyboardAvoidingViewstill wraps the page root.Migration: if your existing payload relied on the implicit page scroll (content taller than the viewport with no
ScrollViewUIElement), wrap your top-level container in aScrollViewelement to restore the previous behavior. Layouts where the root container isflex: 1(the common case) are unaffected — and now render correctly when the inner tree usesflexto share space. -
Rive default size — wrapper height defaults to undefined (was
200); when neitherheight/flex/aspectRatio/min-height/max-heightis set, falls back toaspectRatio: 1so the artboard doesn't fill the screen via its native intrinsic.
Fixed
Buttonhonors explicitpadding: 0— sub-axis defaults (paddingHorizontal: 24,paddingVertical: 14) used to apply even whenpaddingwas set to 0, because RN treats the shorthand and axis props independently. Axis defaults now apply only whenpaddingitself is unset.ButtonhonorstextAlign— Pressable'salignItems: "center"constrained the labelTextto its intrinsic width, neutralizingtextAlign. Removed the constraint so the label stretches andleft | center | rightapplies (default still centered).Buttonshadow visible fromshadowColoralone — iOS defaultsshadowOpacityto 0; the renderer now fills inshadowOpacity: 1andshadowRadius: 4when onlyshadowColoris set.Imageshadow renders — iOS clipped image shadows because theImagehost hadoverflow: hidden. WhenshadowColor/elevationis set, the renderer now wraps the image in a shadow-carryingView(orGradientBox) and lets the innerImageclip its own rounded corners.
[1.25.0] - 2026-05-27
Added
ScrollViewelement renderer — renders a React NativeScrollView. AppliesBaseBoxPropsto the outer container (gradient-aware), mapsbounces/ indicators /contentInset/keyboardShouldPersistTaps, and exposes acontentContainerPaddingshortcut oncontentContainerStyle(which also keepsflexGrow: 1).horizontalrenders children in row order.KeyboardAvoidingViewelement renderer — renders a React NativeKeyboardAvoidingViewwithbehaviordefaulting to iOSpadding/ Androidheight, pluskeyboardVerticalOffsetandenabled.
Changed
- ComposableScreen page wraps content in
KeyboardAvoidingView— the page Renderer now nests its scroll view inside aKeyboardAvoidingView(flex: 1, iOSpadding/ Androidheight), so text inputs avoid the keyboard. AKeyboardAvoidingViewplaced inside the page scroll view is inert by design (it cannot measure its frame); keyboard avoidance is handled at the page level.
[1.24.0] - 2026-05-27
Added
- Button per-state styling + shadow —
ButtonElementrenderer now mergespressedStyle(while held) anddisabledStyle(whiledisabledWhenis truthy) on top of base props, and appliesBaseBoxPropsshadow fields (shadowColor,shadowOffset,shadowOpacity,shadowRadius,elevation) to the outermost wrapper. Opacity transitions between rest/pressed/disabled animate overtransitionDurationMs(default150, native driver); color and shadow changes switch instantly.
Changed
ButtonElementusesPressable+Animated.Viewinstead ofTouchableOpacity, enabling explicit press-state tracking and the animated state transitions. Press feedback defaults toopacity 0.8when nopressedStyle.opacityis set, preserving prior tap feel.disabledBackgroundColor/disabledColordeprecated in favor ofdisabledStyle; kept as fallback whendisabledStyleis omitted.
[1.23.0] - 2026-05-26
Added
renderWhenruntime gating in ComposableScreen —renderElementevaluates the new optionalrenderWhenfield on every UIElement against flattenedctx.variablesand returnsnull(skipping the element and its subtree) when the condition is false. Single gating point covers all 15 element types; container subtrees are skipped naturally because the bail-out runs beforerenderChildrenis invoked.
Changed
- Element defaults overlaid into
ctx.variables—Renderer.tsxnow computes element-declared defaults (Carousel.defaultIndex,RadioGroup.defaultValue,CheckboxGroup.defaultValues,Input.defaultValue,DatePicker.defaultValue) via a tree walk and overlays them ontoRenderContext.variablessynchronously on first render.composableVariableskeeps precedence so user-driven updates aren't clobbered. MakesrenderWhenand{{var}}interpolation see defaults from the very first frame, before per-element seeding effects persist them into the variable store. CarouselElementpersists default index — whenvariableNameis set and the variable has no value yet, the carousel writes its clampeddefaultIndexintocomposableVariableson mount, matching the seeding pattern used by RadioGroup / Input / DatePicker.
Internal
- New
elements/collectDefaults.tsmodule — pure recursive walk over the UIElement tree returningRecord<variableName, ComposableVariableEntry>for defaulted variables. Consumed byRenderer.tsx.
[1.22.0] - 2026-05-11
Added
- Expression mode on
setVariablebutton action — new optionalvalueMode?: "literal" | "expression"andkind?: "int" | "float" | "string"fields onSetVariableButtonAction. In"expression"modevalueis evaluated as an arithmetic expression supporting{{var}}references, numeric literals,+ - * /, and parens. Variable values are coerced according to theirkindtag (string / int / float) or inferred from string content when no tag is present. Numeric+on any string operand becomes concat. Missing variables default to numeric 0 in arithmetic context (so{{counter}} + 1works on first click). On any parse failure the action falls back to plain{{var}}interpolation. Result kind is written back to the variable entry so subsequent expressions can re-evaluate without re-tagging.
Internal
- New
elements/expression.tsmodule — tokenizer + recursive-descent parser for the expression-mode subset. Pure function, no dependencies, deterministic.
[1.21.0] - 2026-05-11
Added
- Variable-bound
Carouselindex — Carousel renderer mirrors the newdefaultIndexandvariableNameschema fields. Initial page resolves from the variable value (whenvariableNameset and parsable as int) then falls back todefaultIndex ?? 0; index is clamped to[0, children.length - 1]and frozen at mount to avoid carousel remounts. AuseEffectwatching the variable value callsref.scrollTo()on external changes (e.g.setVariablebutton actions);onSnapToItemwrites the current index back as a string whenvariableNameis set. AlastSyncedIndexref prevents external↔swipe feedback loops.
[1.20.0] - 2026-05-11
Added
- Disabled-state support on ComposableScreen
Buttonrenderer — the renderer now readsdisabledWhen,disabledBackgroundColor, anddisabledColorfromButtonElementProps. When the condition evaluates truthy againstctx.variables(flattened to primitive values), theTouchableOpacityis disabled and the button renders with the disable color tokens (theme.colors.disable,theme.colors.text.disable) or the per-button overrides. Filled buttons with abackgroundGradientdrop the gradient in the disabled state for a clearer affordance; outlined buttons swap the border to the disable color.
[1.19.0] - 2026-05-07
Added
typography.defaultFontFamilytheme token — new optional field onTypographyTokens. Defaults to"Inter". Override viacustomTheme={{ typography: { defaultFontFamily: "Lobster" } }}to brand every ComposableScreen text element with one font without patching eachtextStyles.*.fontFamilyentry.- Font inheritance on
Text/Button/InputComposableScreen renderers — when an element omitsfontFamilyor sets it to the literal"inherit", the renderer resolves the family againsttheme.typography.defaultFontFamilybefore passing it touseResolvedFontStyle. Resolution helper exported asresolveInheritedFontFamilyfrom the ComposableScreensharedmodule. - New
resolveInheritedFontFamily(elementFontFamily, themeDefault)util atUI/Pages/ComposableScreen/elements/shared.ts.
Changed
ButtonElement,InputElement,TextElementtypings:fontFamily?: string | "inherit"(wasstring).
[1.18.0] - 2026-05-06
Added
fontStylerendering onTextElement,ButtonElement,InputElement(top-level), andRadioGroupElement/CheckboxGroupElement(itemFontStyle). Renderers pass the value through to the underlying<Text>/<TextInput>style, alongsidefontFamilyandfontWeight.setVariableButtonaction —ButtonElementhandles a new action variant{ type: "setVariable", name, value, label? }. The handler writes to the ComposableScreen variable map (and syncs the headless variable map) before any subsequent action in the chain runs, so a following"continue"sees the updated value whenresolveNextStepNumberevaluates branch conditions.
Changed
Button/Text/Inputfont weight resolution — switched fromuseResolvedFontFamilytouseResolvedFontStylefrom@rocapine/react-native-onboarding. When the registry matches a concrete weighted variant (e.g.Inter-700),fontWeightis suppressed on the rendered<Text>to avoid synthetic emboldening on top of an already-weighted font file.
Fixed
CarouselElementsizing — wrap the carousel in an innerView flex:1withonLayoutand pass measuredwidth/heighttoreact-native-reanimated-carouselinstead ofDimensions.get("window"). Render is gated until first measurement.OnboardingDataGateerror handling —useQueryerrors are now thrown so a hostErrorBoundarycatches them, instead of silently rendering thefontsFallbackforever.FontLoaderGate— resets registry to a loading sentinel before async registration and falls back to an empty registry on rejection so a fetch failure doesn't strand the gate.
[1.17.1] - 2026-05-04
Fixed
- Runtime font registration via
OnboardingProvider— fonts declared on the onboarding payload now load correctly when the backend returns the variant-array shape ({ family: [{ weight, style, url }, ...] }). Previous versions silently failed withloadSingleFontAsync expected resource of type Assetand bogusweight 8 from [object Object]warnings, leavingfontFamilystrings unmapped to weighted variants. No UI-package API change; fix lives in the headless SDK consumed byFontLoaderGate.
[1.17.0] - 2026-04-30
Changed
- ComposableScreen typography elements use the runtime font registry —
TextElement,ButtonElement, andInputElementnow calluseResolvedFontFamily(fontFamily, fontWeight)from@rocapine/react-native-onboardingto resolve afamily + weightrequest to the runtime-registered font variant. CMS authors continue to setfontFamilyto the family name declared in theOnboarding.fontsmanifest; the SDK picks the right registered variant (e.g.Inter+500→Inter-500) and falls back to the closest registered weight when an exact match is unavailable.
Element Zod schemas are unchanged. No CMS migration required for existing payloads — they keep working with system fonts.
Bumped
- Peer dependency on
@rocapine/react-native-onboardingis now^1.17.0.
[1.16.0] - 2026-04-29
Added
- Button
actionsexecution —ButtonElementnow runs the headlessButtonAction[]chain on press: sequential,awaits async handlers, warns on missing handler, aborts on thrown error,"continue"is terminal. customActionsplumbing —RenderContextexposescustomActionsto every ComposableScreen element.ComposableScreenRendererreads them from the headlessOnboardingProgressContext(set via<OnboardingProvider customActions={...}>).- Re-exports
ButtonAction,CustomButtonAction,CustomActionHandler,CustomActions,ComposableVariableEntryfrom the headless package.
Changed
ComposableVariableEntryis now sourced from the headless package (@rocapine/react-native-onboarding); the UI provider re-exports it. Existing imports fromOnboardingProgressProvidercontinue to work.
[1.15.0] - 2026-04-28
Added
SafeAreaViewUIElement renderer — newSafeAreaViewElementComponentthat delegates toSafeAreaViewfromreact-native-safe-area-context. Forwardsmodeandedges(array or per-edge object) and appliesBaseBoxPropsstyling.
Changed
OnboardingTemplateno longer applies safe-area insets. The template previously readuseSafeAreaInsets()and addedpaddingTop/paddingBottom. Renderers now own safe-area handling:Carousel,Commitment,Loader,MediaContent,Picker,Question, andRatingswrap their content with<SafeAreaView edges={["top", "bottom"]}>. TheComposableScreenrenderer intentionally does not wrap — author safe-area placement using the newSafeAreaViewUIElement so screens can render edge-to-edge backgrounds.- The progress-header offset (40px) remains in
OnboardingTemplateas plain padding, no longer combined with the top inset.
[1.14.0] - 2026-04-28
Added
ZStackUIElement renderer — newZStackElementComponentthat renders children layered on top of each other. Each child is wrapped inposition: "absolute"filling the container, enabling image-with-text-overlay and other depth-compositing patterns. Supports allBaseBoxPropsincludingbackgroundGradientviaGradientBox.
[1.13.1] - 2026-04-28
Added
ZStackUIElement renderer — newZStackElementComponentthat renders children layered on top of each other. Each child is wrapped inposition: "absolute"filling the container, enabling image-with-text-overlay and other depth-compositing patterns. Supports allBaseBoxPropsincludingbackgroundGradientviaGradientBox.
[1.13.0] - 2026-04-28
Added
-
Gradient backgrounds on all
ComposableScreenelements — every element that renders a container (YStack,XStack,Icon,Image,Text,Button,Lottie,Video,RadioGroup,CheckboxGroup,Carousel,DatePicker) now respectsbackgroundGradientfromBaseBoxProps. -
GradientBoxcomponent — internal utility that wrapsexpo-linear-gradient(LinearGradient) when the library is installed, falling back to a plainViewsilently when it is not. All element renderers delegate their outer container toGradientBox. -
expo-linear-gradientoptional peer dependency — install it to enable gradient rendering; omitting it degrades gracefully to a solid background. -
Linear gradient API —
backgroundGradient: { type: "linear", from: GradientEdge, to: GradientEdge, stops: GradientStop[] }.GradientEdgeis one of 8 named positions ("top","bottom","left","right","topLeft","topRight","bottomLeft","bottomRight"). Stops support optional explicitposition(0–1); when all stops declare a position,locationsis passed toLinearGradient.
Fixed
figmaUrltype inComposableScreenstep schema — changed from.nullable()to.nullish()to align with all other page-type schemas and the headless SDK.
[1.12.0] - 2026-04-28
Changed
ComposableScreenelement variable sync — when aComposableScreenelement with avariableName(e.g.Input,RadioGroup,DatePicker,CheckboxGroup) changes its value, the change is now written to both the UI-layercomposableVariablesstore (drives{{interpolation}}within the current screen) and the headlessvariablesstore (OnboardingProgressContext.setVariable). This makes composable element answers available toresolveNextStepNumberbranch conditions on subsequent steps.
[1.11.1] - 2026-04-27
Changed
-
All element renderers updated to apply the full expanded
BaseBoxProps:minWidth,maxWidth,minHeight,maxHeight,flexShrink,flexGrow,backgroundColor,overfloware now wired into every element's style output. -
dim()helper added (shared.ts) — castsnumber | stringwidth/height values to React Native'sDimensionValue, enabling percentage strings (e.g."100%") across all elements. -
StackElementrenderer — appliesflexGrow, all newBaseBoxPropslayout props.width/heightnow support percentage strings. -
TextElementrenderer — appliesflex,flexShrink/flexGrow,alignSelf,width/height(viadim()),minWidth/maxWidth/minHeight/maxHeight,overflow. -
InputElementrenderer — appliesfontFamily,lineHeight,letterSpacing; alsoflex,flexShrink/flexGrow,minWidth/maxWidth/minHeight/maxHeight,overflow. -
ButtonElementrenderer —alignSelfnow uses the complete enum fromBaseBoxProps. -
RiveElementrenderer — prop renamedautoplay→autoPlay(schema-level rename; the underlyingrive-react-nativelibrary still receivesautoplay). -
CarouselElementrenderer —Pagination.Basicnow driven by dot style props:dotColor,activeDotColor,dotWidth,dotHeight,dotsGap,dotsMarginTop. -
IconElement,LottieElement,VideoElementrenderers — applyflex,flexShrink/flexGrow,alignSelf,minWidth/maxWidth/minHeight/maxHeight.
[1.11.0] - 2026-04-24
Added
-
Carouselelement renderer — rendersCarouselUIElements usingreact-native-reanimated-carousel(now a required peer dependency). Each slide is aUIElementsubtree rendered by the same recursive engine asYStack/XStack, giving full layout flexibility per slide. Four modes viacarouselType:"normal"— full-width paged carousel (default)"parallax"— depth-zoom effect using librarymode="parallax""stack"— stacked cards at 75 % window width viamode="horizontal-stack""left-align"— peek effect at 82 % window width withoverflow: "visible"
Pagination uses
Pagination.Basicfrom the library: animated pill dots in themeprimary/neutral.lowcolors, tappable to jump to any slide.autoPlaydefaults tofalse;loopdefaults totrue;showDotsdefaults totrue. Width defaults touseWindowDimensions().width; height defaults to220 px. AllBaseBoxPropsapplied to the outer container.
[1.10.0] - 2026-04-23
Added
DatePickerelement renderer — rendersDatePickerUIElements using@react-native-community/datetimepicker(new optional peer dependency). On mount, initialises the variable fromdefaultValue(or today if omitted) as{ value: ISO string, label: locale-formatted string }. On change, updates the same variable; thelabelis human-readable (e.g."Apr 23, 2026"formode: "date"). SupportsminimumDate,maximumDate,mode(date/time/datetime),display(platform-specific — iOS defaults to"spinner", Android to"default"),textColor,accentColor,locale, and allBaseBoxPropsfor the wrapping container.
[1.9.0] - 2026-04-22
Added
CheckboxGroupelement renderer — rendersCheckboxGroupUIElements as a vertical (default) or horizontal list of tappable checkbox items. Each item shows a square checkbox indicator and a label; tapping toggles the item's value in/out of the selected set. On mount, setsdefaultValuesintocomposableVariables(keyed byvariableName) as{ value: JSON.stringify(string[]), label: string }. Subsequent toggles update the same entry. Supports all per-item style props (itemBackgroundColor,itemSelectedBackgroundColor,itemBorderColor,itemSelectedBorderColor,itemBorderRadius,itemBorderWidth,itemColor,itemSelectedColor,itemFontSize,itemFontWeight,itemFontFamily,itemPadding,itemPaddingHorizontal,itemPaddingVertical),gap,direction, and allBaseBoxPropsfor the group container.
[1.8.1] - 2026-04-22
Added
alignSelfon allBaseBoxPropselements —Input,RadioGroup,Image,Lottie,Rive,Icon, andVideorenderers now passalignSelffrom props to their root style. Accepts"auto" | "flex-start" | "flex-end" | "center" | "stretch" | "baseline".alignSelfonStackElement—YStack/XStackrootViewnow appliesalignSelffrom props.
Fixed
InputElementflattened to bare<TextInput>— removed the wrapping<View>soalignSelf,width,height, and other layout props apply directly to the input rather than a container. All style props previously split between the wrapper and the innerTextInputare now on the singleTextInput.RadioGroupitem text collapse — replacedflex: 1withflexShrink: 1on the label<Text>inside each radio item. Prevents Yoga from collapsing the text when the item is inside anXStack.
[1.8.0] - 2026-04-21
Added
Buttonelement renderer — rendersButtonUIElements as a<TouchableOpacity>. Supports three variants:filled(solid primary background),outlined(transparent background with border), andghost(no background or border). Tapping callsonContinuewhenactionis"continue"or unset; other future action values are no-ops. Supportslabel,variant,backgroundColor,color,fontSize,fontWeight,fontFamily,textAlign,alignSelf, and allBaseBoxProps.RadioGroupelement renderer — rendersRadioGroupUIElements as a vertical (default) or horizontal list of tappable radio items, each with a circular indicator. Reads/writes the selected value viacomposableVariables(keyed byvariableName). On mount, sets thedefaultValueentry including the matching item's human-readablelabel. Supports all per-item style props (itemBackgroundColor,itemSelectedBackgroundColor,itemBorderColor,itemSelectedBorderColor,itemBorderRadius,itemBorderWidth,itemColor,itemSelectedColor,itemFontSize,itemFontWeight,itemFontFamily,itemPadding,itemPaddingHorizontal,itemPaddingVertical) and allBaseBoxPropsfor the group container.- Structured variable entries —
composableVariablesis nowRecord<string, ComposableVariableEntry>whereComposableVariableEntry = { value: string; label?: string }.RadioGroupstores{ value, label }on selection;Inputstores{ value }. Expression interpolation inTextelements resolveslabel ?? value, so{{variableName}}on a radio-backed variable displays the human-readable label (e.g."Monthly") instead of the raw value (e.g."monthly").
Note on semver: The
composableVariablestype changed fromRecord<string, string>toRecord<string, ComposableVariableEntry>. This is a technically breaking change to the context shape, but is published as a minor bump becausecomposableVariablesis an internal context value (not part of the public API contract). Existing consumers that only read the value string viavariables[key]remain unaffected — access.valuefor the same result.
Changed (internal)
ComposableScreenelement components and types split intoelements/subfolder — one file per element.Renderer.tsxreduced from 630 to 58 lines;types.tsfrom 443 to 173 lines. ARenderContextobject replaces the five individual parameters previously threaded throughrenderElement.
[1.7.0] - 2026-04-21
Added
fontFamilysupport onTextelements — theTextrenderer now passesfontFamilyfrom element props directly to the React Native<Text>style. Any font family loaded by the host app (e.g. viaexpo-font) can be applied to a text node by settingfontFamilyin its props.
[1.6.0] - 2026-04-21
Added
Inputelement renderer — rendersInputUIElements as a styled<TextInput>. Supports all text input props (placeholder,placeholderColor,defaultValue,keyboardType,returnKeyType,autoCapitalize,secureTextEntry,maxLength,multiline,numberOfLines,editable) plus typography (color,fontSize,textAlign,padding*) andBaseBoxProps(backgroundColor,borderWidth,borderRadius,borderColor,width,height,opacity,margin*).- Variable context —
OnboardingProgressContextextended withcomposableVariables: Record<string, string>andsetComposableVariable.Inputelements write their value into this shared map on every keystroke (keyed byvariableName). Values survive navigation betweenComposableScreensteps because the context lives above the router. - Expression interpolation for
Textelements — whenmode: "expression",{{variableName}}patterns incontentare replaced with values fromcomposableVariablesat render time. Defaultmode: "plain"is unchanged. OnboardingProgressProviderandOnboardingProgressContextexported from the package's public API so host apps can wrap their root layout with the provider.
Fixed
InputElementComponentno longer subscribes toOnboardingProgressContextdirectly;setComposableVariableis threaded as a stable prop throughrenderElementinstead, preventing context-driven re-renders from stealingTextInputfocus on every keystroke.ComposableScreenStepTypeSchema.parse(step)is now wrapped inuseMemoso theelementsarray reference is stable across context-driven re-renders.ScrollViewinComposableScreenRenderernow useskeyboardShouldPersistTaps="handled"so a first tap on anInputinside aScrollViewcorrectly focuses the field rather than being swallowed.
[1.5.0] - 2026-04-21
Added
Iconelement renderer — rendersIconUIElements usinglucide-react-native(bundled, no extra install needed). Supportsname,size,color,strokeWidth, and allBaseBoxProps. Unknown icon names render nothing rather than crashing.Videoelement renderer — rendersVideoUIElements viaexpo-video(optional peer dep). Supportsurl,autoPlay,loop,muted,controls, and allBaseBoxProps. Shows an install-hint placeholder ifexpo-videois absent.expo-videoadded as optional peer dependency.
[1.4.0] - 2026-04-21
Added
Lottieelement renderer — rendersLottieUIElements vialottie-react-native. The package is an optional peer dep; if absent a placeholder view with an install hint is shown instead of crashing. Supportssource,autoPlay,loop,speed, and allBaseBoxProps.Riveelement renderer — rendersRiveUIElements viarive-react-native(optional peer dep with same graceful fallback). Supportsurl,autoplay,fit,alignment,artboardName,stateMachineName, and allBaseBoxProps.
Changed
BaseBoxPropsrefactor —width,height,opacity,margin*,padding*,borderWidth,borderRadius, andborderColorare now defined once in a sharedBaseBoxPropstype andBaseBoxPropsSchema, then extended byImage,Lottie, andRiveelement schemas.
Fixed
borderWidth,borderRadius, andborderColoronLottieandRiveelements now render correctly. Both native canvas components are wrapped in aViewwithoverflow: hiddenso border styles are applied by the wrapper rather than the animation view directly.
[1.3.0] - 2026-04-17
Added
ImageUIElement renderer forComposableScreen— mapsImagenodes to React Native<Image>with full prop pass-through:url,width,height,aspectRatio,resizeMode,borderRadius,borderWidth,borderColor,opacity, and all margin / padding shorthand props.aspectRatiofallback onImage— whenheightis not provided, the renderer appliesaspectRatio(explicit value or16/9default) so the image is always visible.
Fixed
- Removed unused
useSafeAreaInsetsimport and call fromComposableScreenRenderer(safe area is handled byOnboardingTemplate).
[1.2.0]
Added
- ComposableScreen renderer (under development) — renders the new
ComposableScreenstep type by recursively walking aUIElementtree and mapping each node to a nativeVieworText. The renderer now passes through all new layout props added in this release:borderWidth,borderRadius,borderColor,overflow,opacity,margin,marginHorizontal,marginVertical,width,height,minWidth,maxWidth,minHeight,maxHeighton stack elements;margin,marginHorizontal,marginVertical,borderWidth,borderRadius,borderColor, andopacityon text elements. packages/onboarding-ui/README.md— new README documenting the UI package, theComposableScreenelement tree API, and its supported props.
Note:
ComposableScreenis under active development. The renderer and element schema may change before they are considered stable.