Bloom is the UI library every Oxy app is built with.
One component set for React Native, Expo and the web, with the same props on every platform.
|
Components, hooks and design tokens published as Styling is NativeWind classes throughout. There are no colour props and no wrapper components to theme a button, because a second way to set a colour is a second thing that can disagree. |
Bloom is the presentation layer of oxy. It knows nothing about identity or the network, so it has no dependency on the Oxy SDK and can be used on its own. Where an app does use the SDK, the two meet at one seam: register |
bun add @oxyhq/bloomPeer dependencies
Always required:
| Package | Range |
|---|---|
react |
>=18.0.0 |
react-dom |
>=18.0.0 |
react-native |
>=0.73.0 |
react-native-safe-area-context |
>=5.0.0 |
react-native-screens |
>=3.16.0 |
nativewind |
>=5.0.0 |
Required by specific surfaces, and by more of them than you would guess:
| Package | Range | Needed by |
|---|---|---|
react-native-reanimated |
>=3.13.0 |
Dialog, BottomSheet, toast, Loading, on the web too |
react-native-gesture-handler |
>=2.16.1 |
Dialog, BottomSheet, swipe to dismiss on toasts |
react-native-svg |
>=13.0.0 |
icons, Avatar in squircle shape |
react-native-keyboard-controller |
>=1.11.4 |
keyboard aware surfaces |
expo, expo-blur, expo-font, expo-haptics, expo-image, expo-symbols |
see package.json |
native effects, fonts, haptics, images and SF Symbols |
expo-glass-effect |
>=0.1.9 |
glass surfaces |
expo-router |
>=3.0.0 |
the expo-router variants of tabs, scroll and tab-bar |
@react-native-community/netinfo |
>=11.1.0 |
connection-status |
On native, wrap the app root in GestureHandlerRootView from react-native-gesture-handler, or the bottom sheet pan gestures will not fire.
Mount BloomProvider once, at the very top of the app. It composes every piece of app wide Bloom state, theme, haptics, image resolution, scroll restoration and tab bar minimise progress, so none of them can end up at the wrong depth.
import { BloomProvider } from '@oxyhq/bloom/provider';
<BloomProvider
defaultMode="system"
defaultColorPreset="blue"
persistKey="app.theme"
storage={storage}
imageResolver={(id, variant) => oxyServices.getFileDownloadUrl(id, variant)}
>
<App />
</BloomProvider>Everything scrollable must sit under it. useScrollRestoration() throws outside its provider, so a list rendered beside the root, a right rail or an overlay, crashes that screen.
Outlets are deliberately not included, because where they sit in the tree is a real application decision and a second mount duplicates every surface they render. Mount these yourself, under BloomProvider: ToastOutlet, the Provider and Outlet pair from @oxyhq/bloom/portal, SurfaceHost, BloomDialogProvider and AlertDialogHost.
import { Button } from '@oxyhq/bloom/button';
import { toast } from '@oxyhq/bloom/toast';
<Button onPress={() => toast.success('Saved')}>Save</Button>|
Four modes
import { useTheme } from '@oxyhq/bloom/theme';
const theme = useTheme();
// theme.colors.primary, theme.colors.text, theme.isDark
|
Eighteen colour presets
Every palette is generated from a single seed colour by a dependency free colour engine, into the full Material 3 role set for light and dark. A colour a user picks themselves runs through exactly the same path, so a preset is only a fixed seed. |
Bloom publishes 88 subpath exports. Importing from the subpath rather than the root keeps a bundle to what it actually renders.
| Group | Exports |
|---|---|
| Providers and theme | provider, theme, color-presets, preset-vars, design-tokens, tailwind-preset, styles, hooks |
| Overlays | dialog, alert-dialog, bottom-sheet, popover, context-menu, menu, tooltip, overlay, portal, surfaces |
| Actions | button, fab, frosted-icon-button, grouped-buttons, pressable-scale, pressable-with-hover, subtle-hover |
| Forms | text-field, field, input-group, label, select, combobox, command, checkbox, switch, slider, segmented-control, search, prompt-input |
| Layout and lists | grid, list, scroll, tabs, tab-bar, settings-list, content-panel, card, accordion, collapsible, divider, item |
| Identity and media | avatar, avatar-group, user-hover-card, profile-card, image-resolver, image-aspect-ratio-cache, zoomable-image-gallery, media-inset-border, progressive-blur, fill |
| Feedback and data | toast, admonition, loading, skeleton, error-boundary, badge, chip, kbd, code, link-preview, connection-status, connection-dots |
| Charts and motion | composition-bar, dot-grid-meter, stat-bar, activity-heatmap, motion, animated-check, icon-circle, radio-indicator |
| Assets | icons, typography, fonts, benefit-list |
tabs, scroll and tab-bar each ship an /expo-router variant for apps on Expo Router.
Component guides live in docs/: getting started, theme, design tokens, dialog, bottom sheet, toast, alert, menu, select, context menu, button, avatar, text field, settings list, tab bar, prompt input and loading.
Upgrade notes are in MIGRATION.md, and the theme reference is in README.theme.md.
bun install
bun run build # react-native-builder-bob, then verify the published shape
bun run typescript # type check
bun run test # jest
bun run storybook # component workshop on port 6006
bun run clean # remove lib/build regenerates the platform export map and the theme CSS first, then verifies the package it just produced. If that verification fails, the package is wrong, not the check.
Issues and pull requests are welcome. Please run bun run typescript and bun run test first. Org wide contributing notes, the security policy and the code of conduct live in the organisation profile.
Apache-2.0, The Oxy Collective, Inc. See LICENSE.
Bloom moved from AGPL-3.0-only to Apache-2.0 at 0.87.0. Versions published before that keep AGPL-3.0-only permanently; a licence change binds future versions only.
Third party code Bloom derives from, principally the universal toast engine, is credited in NOTICE.