Bluesky's "Application Layout Framework"
at docs 825 lines 22 kB view raw view rendered
1# API Reference 2 3Complete reference for `@bsky.app/alf`. All exports come from a single entry point: 4 5```typescript 6import { 7 atoms, 8 useTheme, 9 Provider, 10 Context, 11 tokens, 12 utils, 13 themes, 14 createTheme, 15 createThemes, 16 DEFAULT_PALETTE, 17 DEFAULT_SUBDUED_PALETTE, 18 invertPalette, 19 isWeb, 20 isNative, 21 isIOS, 22 isAndroid, 23 isFabric, 24 web, 25 native, 26 ios, 27 android, 28 platform, 29} from '@bsky.app/alf' 30 31import type { 32 Palette, 33 Theme, 34 ThemeScheme, 35 ThemeName, 36 ThemeAtoms, 37 TextStyleProp, 38 ViewStyleProp, 39} from '@bsky.app/alf' 40``` 41 42--- 43 44## Provider and Hook 45 46### `Provider` 47 48React context provider that makes the active theme available to all children via `useTheme()`. 49 50```tsx 51<Provider activeTheme="light" themes={themes}> 52 <App /> 53</Provider> 54``` 55 56| Prop | Type | Description | 57|------|------|-------------| 58| `activeTheme` | `T extends string` | Key into the `themes` object | 59| `themes` | `Record<T, Theme>` | Map of theme name to `Theme` object | 60| `children` | `ReactNode` | Your app | 61 62The provider memoizes the context value based on `activeTheme` and `themes`. 63 64### `useTheme()` 65 66Returns the active `Theme` object from context. You access theme-aware color atoms from `t.atoms`: 67 68```tsx 69const t = useTheme() 70<View style={[t.atoms.bg, t.atoms.shadow_md]}> 71 <Text style={[t.atoms.text]}>Themed text</Text> 72</View> 73``` 74 75Returns: `Theme` 76 77### `Context` 78 79The raw React context (`React.Context<{ theme: Theme }>`). Initial value uses `themes.light`. Display name: `'AlfContext'`. 80 81You rarely need this directly. Use `useTheme()` instead. 82 83--- 84 85## Atoms 86 87All atoms live on the `atoms` object. They are frozen, static style objects that you combine via arrays. 88 89```tsx 90import { atoms as a } from '@bsky.app/alf' 91 92<View style={[a.flex_row, a.gap_md, a.p_lg, a.rounded_md]} /> 93``` 94 95### Naming conventions 96 97- Underscore prefix for sizes starting with a number: `_2xs`, `_2xl`, `_3xl`, etc. 98- Axis suffixes: `x` (left + right), `y` (top + bottom) 99- Side suffixes: `t` (top), `b` (bottom), `l` (left), `r` (right) 100 101### Debug 102 103| Atom | Style | 104|------|-------| 105| `debug` | `borderColor: 'red', borderWidth: 1` | 106 107### Positioning 108 109| Atom | Style | 110|------|-------| 111| `fixed` | `position: 'fixed'` (native: `'absolute'`) | 112| `absolute` | `position: 'absolute'` | 113| `relative` | `position: 'relative'` | 114| `static` | `position: 'static'` | 115| `sticky` | `position: 'sticky'` (native: empty) | 116| `inset_0` | `top/right/bottom/left: 0` | 117| `top_0` | `top: 0` | 118| `right_0` | `right: 0` | 119| `bottom_0` | `bottom: 0` | 120| `left_0` | `left: 0` | 121 122### Z-index 123 124| Atom | Value | 125|------|-------| 126| `z_10` | `zIndex: 10` | 127| `z_20` | `zIndex: 20` | 128| `z_30` | `zIndex: 30` | 129| `z_40` | `zIndex: 40` | 130| `z_50` | `zIndex: 50` | 131 132### Overflow 133 134| Atom | Style | 135|------|-------| 136| `overflow_visible` | `overflow: 'visible'` | 137| `overflow_hidden` | `overflow: 'hidden'` | 138| `overflow_auto` | `overflow: 'auto'` (native: empty) | 139| `overflow_x_visible` | `overflowX: 'visible'` | 140| `overflow_x_hidden` | `overflowX: 'hidden'` | 141| `overflow_y_visible` | `overflowY: 'visible'` | 142| `overflow_y_hidden` | `overflowY: 'hidden'` | 143 144### Width and height 145 146| Atom | Style | 147|------|-------| 148| `w_full` | `width: '100%'` | 149| `h_full` | `height: '100%'` | 150| `h_full_vh` | `height: '100vh'` | 151| `max_w_full` | `maxWidth: '100%'` | 152| `max_h_full` | `maxHeight: '100%'` | 153 154### Border radius 155 156Values come from `tokens.borderRadius` (zero variants use literal `0`). 157 158| Atom | Value (px) | 159|------|------------| 160| `rounded_0` | 0 | 161| `rounded_2xs` | 2 | 162| `rounded_xs` | 4 | 163| `rounded_sm` | 8 | 164| `rounded_md` | 12 | 165| `rounded_lg` | 16 | 166| `rounded_xl` | 20 | 167| `rounded_full` | 999 | 168 169### Flexbox 170 171| Atom | Style | 172|------|-------| 173| `flex` | `display: 'flex'` | 174| `flex_col` | `flexDirection: 'column'` | 175| `flex_row` | `flexDirection: 'row'` | 176| `flex_col_reverse` | `flexDirection: 'column-reverse'` | 177| `flex_row_reverse` | `flexDirection: 'row-reverse'` | 178| `flex_wrap` | `flexWrap: 'wrap'` | 179| `flex_nowrap` | `flexWrap: 'nowrap'` | 180| `flex_0` | `flex: '0 0 auto'` (native: `flex: 0`) | 181| `flex_1` | `flex: 1` | 182| `flex_grow` | `flexGrow: 1` | 183| `flex_grow_0` | `flexGrow: 0` | 184| `flex_shrink` | `flexShrink: 1` | 185| `flex_shrink_0` | `flexShrink: 0` | 186 187### Alignment 188 189| Atom | Style | 190|------|-------| 191| `justify_start` | `justifyContent: 'flex-start'` | 192| `justify_center` | `justifyContent: 'center'` | 193| `justify_between` | `justifyContent: 'space-between'` | 194| `justify_end` | `justifyContent: 'flex-end'` | 195| `align_start` | `alignItems: 'flex-start'` | 196| `align_center` | `alignItems: 'center'` | 197| `align_end` | `alignItems: 'flex-end'` | 198| `align_baseline` | `alignItems: 'baseline'` | 199| `align_stretch` | `alignItems: 'stretch'` | 200| `self_auto` | `alignSelf: 'auto'` | 201| `self_start` | `alignSelf: 'flex-start'` | 202| `self_end` | `alignSelf: 'flex-end'` | 203| `self_center` | `alignSelf: 'center'` | 204| `self_stretch` | `alignSelf: 'stretch'` | 205| `self_baseline` | `alignSelf: 'baseline'` | 206 207### Gap 208 209Values come from `tokens.space` (zero variants use literal `0`). 210 211| Atom | Value (px) | 212|------|------------| 213| `gap_0` | 0 | 214| `gap_2xs` | 2 | 215| `gap_xs` | 4 | 216| `gap_sm` | 8 | 217| `gap_md` | 12 | 218| `gap_lg` | 16 | 219| `gap_xl` | 20 | 220| `gap_2xl` | 24 | 221| `gap_3xl` | 28 | 222| `gap_4xl` | 32 | 223| `gap_5xl` | 40 | 224 225### Typography 226 227#### Font size 228 229Each atom sets both `fontSize` and `letterSpacing: 0`. Values come from `tokens.fontSize`. 230 231| Atom | Size (px) | 232|------|-----------| 233| `text_2xs` | 9.4 | 234| `text_xs` | 11.3 | 235| `text_sm` | 13.1 | 236| `text_md` | 15 | 237| `text_lg` | 16.9 | 238| `text_xl` | 18.8 | 239| `text_2xl` | 20.6 | 240| `text_3xl` | 24.3 | 241| `text_4xl` | 30 | 242| `text_5xl` | 37.5 | 243 244#### Text alignment 245 246| Atom | Style | 247|------|-------| 248| `text_left` | `textAlign: 'left'` | 249| `text_center` | `textAlign: 'center'` | 250| `text_right` | `textAlign: 'right'` | 251 252#### Line height 253 254Values are unitless multipliers from `tokens.lineHeight`. Use the `utils.leading()` function when you need computed pixel values on native. 255 256| Atom | Multiplier | 257|------|------------| 258| `leading_tight` | 1.15 | 259| `leading_snug` | 1.3 | 260| `leading_relaxed` | 1.5 | 261| `leading_normal` | 1.5 (deprecated, use `leading_relaxed`) | 262 263#### Letter spacing 264 265| Atom | Style | 266|------|-------| 267| `tracking_normal` | `letterSpacing: 0` | 268 269#### Font weight 270 271| Atom | Weight | 272|------|--------| 273| `font_normal` | `'400'` | 274| `font_medium` | `'500'` | 275| `font_semi_bold` | `'600'` | 276| `font_bold` | `'700'` | 277 278#### Font style 279 280| Atom | Style | 281|------|-------| 282| `italic` | `fontStyle: 'italic'` | 283 284### Borders 285 286On native, all 1px border atoms use `StyleSheet.hairlineWidth` instead. See the [platform behavior](#native-overrides) section. 287 288#### Border width 289 290| Atom | Sides | Width | 291|------|-------|-------| 292| `border_0` | All | 0 | 293| `border` | All | 1 | 294| `border_t_0` / `border_t` | Top | 0 / 1 | 295| `border_b_0` / `border_b` | Bottom | 0 / 1 | 296| `border_l_0` / `border_l` | Left | 0 / 1 | 297| `border_r_0` / `border_r` | Right | 0 / 1 | 298| `border_x_0` / `border_x` | Left + Right | 0 / 1 | 299| `border_y_0` / `border_y` | Top + Bottom | 0 / 1 | 300 301#### Border color 302 303| Atom | Style | 304|------|-------| 305| `border_transparent` | `borderColor: 'transparent'` | 306 307For theme-aware border colors, use `t.atoms.border_contrast_low`, `t.atoms.border_contrast_medium`, or `t.atoms.border_contrast_high`. 308 309### Border curves (iOS only) 310 311These resolve to empty objects `{}` on web. On native, they use the `ios()` platform selector, returning the style on iOS and `undefined` on Android. 312 313| Atom | iOS Style | 314|------|-----------| 315| `curve_circular` | `borderCurve: 'circular'` | 316| `curve_continuous` | `borderCurve: 'continuous'` | 317 318### Shadows 319 320Static shadow atoms are empty objects on web and Fabric. Use theme shadow atoms (`t.atoms.shadow_sm`, etc.) for cross-platform shadows with proper theme colors. 321 322| Atom | Description | 323|------|-------------| 324| `shadow_sm` | Small shadow (native only, disabled on Fabric) | 325| `shadow_md` | Medium shadow (native only, disabled on Fabric) | 326| `shadow_lg` | Large shadow (native only, disabled on Fabric) | 327 328### Gutters 329 330Semantic padding shortcuts. Each has `_x` (horizontal) and `_y` (vertical) variants. 331 332| Atom | Padding (px) | 333|------|-------------| 334| `gutter_tight` / `gutter_x_tight` / `gutter_y_tight` | 8 | 335| `gutter_snug` / `gutter_x_snug` / `gutter_y_snug` | 12 | 336| `gutter_default` / `gutter_x_default` / `gutter_y_default` | 16 | 337| `gutter_wide` / `gutter_x_wide` / `gutter_y_wide` | 20 | 338| `gutter_extra_wide` / `gutter_x_extra_wide` / `gutter_y_extra_wide` | 24 | 339 340### Padding 341 342Values come from `tokens.space` (zero variants use literal `0`). Each size has `p_`, `px_`, `py_`, `pt_`, `pb_`, `pl_`, `pr_` variants. 343 344| Size | Value (px) | 345|------|------------| 346| `0` | 0 | 347| `2xs` | 2 | 348| `xs` | 4 | 349| `sm` | 8 | 350| `md` | 12 | 351| `lg` | 16 | 352| `xl` | 20 | 353| `2xl` | 24 | 354| `3xl` | 28 | 355| `4xl` | 32 | 356| `5xl` | 40 | 357 358Full atom names follow the pattern: `p_md`, `px_lg`, `py_sm`, `pt_xl`, `pb_2xl`, `pl_xs`, `pr_3xl`. 359 360### Margin 361 362Values come from `tokens.space` (zero variants use literal `0`). Each size has `m_`, `mx_`, `my_`, `mt_`, `mb_`, `ml_`, `mr_` variants. 363 364| Size | Value (px) | 365|------|------------| 366| `0` | 0 | 367| `2xs` | 2 | 368| `xs` | 4 | 369| `sm` | 8 | 370| `md` | 12 | 371| `lg` | 16 | 372| `xl` | 20 | 373| `2xl` | 24 | 374| `3xl` | 28 | 375| `4xl` | 32 | 376| `5xl` | 40 | 377 378Full atom names follow the pattern: `m_md`, `mx_lg`, `my_sm`, `mt_xl`, `mb_2xl`, `ml_xs`, `mr_3xl`. 379 380Auto margins: `m_auto`, `mx_auto`, `my_auto`, `mt_auto`, `mb_auto`, `ml_auto`, `mr_auto`. 381 382### Pointer events and user select 383 384| Atom | Style | 385|------|-------| 386| `pointer_events_none` | `pointerEvents: 'none'` | 387| `pointer_events_auto` | `pointerEvents: 'auto'` | 388| `pointer_events_box_only` | `pointerEvents: 'box-only'` | 389| `pointer_events_box_none` | `pointerEvents: 'box-none'` | 390| `user_select_none` | `userSelect: 'none'` | 391| `user_select_text` | `userSelect: 'text'` | 392| `user_select_all` | `userSelect: 'all'` | 393| `outline_inset_1` | `outlineOffset: -1` | 394 395### Text decoration 396 397| Atom | Style | 398|------|-------| 399| `underline` | `textDecorationLine: 'underline'` | 400| `strike_through` | `textDecorationLine: 'line-through'` | 401 402### Display 403 404| Atom | Style | Platform | 405|------|-------|----------| 406| `hidden` | `display: 'none'` | All | 407| `contents` | `display: 'contents'` | All | 408| `inline` | `display: 'inline'` | Web only (native: empty) | 409| `block` | `display: 'block'` | Web only (native: empty) | 410 411### Cursor 412 413| Atom | Style | Platform | 414|------|-------|----------| 415| `pointer` | `cursor: 'pointer'` | Web only (native: empty) | 416 417--- 418 419## Theme Atoms 420 421These live on `t.atoms` (where `t = useTheme()`). They adapt to the active theme's color palette. 422 423Theme atoms only cover the **contrast** color scale — the neutral grays used for text, backgrounds, and borders on nearly every component. Primary, positive, and negative colors are more situational, so they are accessed directly from the palette instead: 424 425```tsx 426const t = useTheme() 427 428// Contrast colors → theme atoms 429<View style={[t.atoms.bg, t.atoms.border_contrast_medium]}> 430 <Text style={[t.atoms.text]}>Neutral text</Text> 431</View> 432 433// Primary / positive / negative → palette 434<View style={{ backgroundColor: t.palette.primary_500 }}> 435 <Text style={{ color: t.palette.positive_500 }}>Success</Text> 436</View> 437``` 438 439Palette values still adapt to the active theme automatically via `invertPalette()`, so `t.palette.primary_500` returns the correct shade in light, dark, and dim modes. 440 441### Text 442 443| Atom | Palette source | 444|------|---------------| 445| `t.atoms.text` | `contrast_1000` | 446| `t.atoms.text_contrast_low` | `contrast_400` | 447| `t.atoms.text_contrast_medium` | `contrast_700` | 448| `t.atoms.text_contrast_high` | `contrast_900` | 449| `t.atoms.text_inverted` | `contrast_0` | 450 451### Background 452 453| Atom | Palette source | 454|------|---------------| 455| `t.atoms.bg` | `contrast_0` | 456| `t.atoms.bg_contrast_25` | `contrast_25` | 457| `t.atoms.bg_contrast_50` | `contrast_50` | 458| `t.atoms.bg_contrast_100` | `contrast_100` | 459| `t.atoms.bg_contrast_200` | `contrast_200` | 460| `t.atoms.bg_contrast_300` | `contrast_300` | 461| `t.atoms.bg_contrast_400` | `contrast_400` | 462| `t.atoms.bg_contrast_500` | `contrast_500` | 463| `t.atoms.bg_contrast_600` | `contrast_600` | 464| `t.atoms.bg_contrast_700` | `contrast_700` | 465| `t.atoms.bg_contrast_800` | `contrast_800` | 466| `t.atoms.bg_contrast_900` | `contrast_900` | 467| `t.atoms.bg_contrast_950` | `contrast_950` | 468| `t.atoms.bg_contrast_975` | `contrast_975` | 469 470### Border 471 472| Atom | Palette source | 473|------|---------------| 474| `t.atoms.border_contrast_low` | `contrast_100` | 475| `t.atoms.border_contrast_medium` | `contrast_200` | 476| `t.atoms.border_contrast_high` | `contrast_300` | 477 478### Shadow 479 480| Atom | Description | 481|------|-------------| 482| `t.atoms.shadow_sm` | Small shadow with theme-appropriate opacity | 483| `t.atoms.shadow_md` | Medium shadow | 484| `t.atoms.shadow_lg` | Large shadow | 485 486Shadow atoms include native shadow props and a `boxShadow` CSS string for web. Light themes use 0.1 shadow opacity, dark/dim themes use 0.4. 487 488--- 489 490## Tokens 491 492Imported as a namespace: `import { tokens } from '@bsky.app/alf'`. All values are pixel-based numbers (no rem/em). 493 494### `tokens.space` 495 496| Key | Value | 497|-----|-------| 498| `_2xs` | 2 | 499| `xs` | 4 | 500| `sm` | 8 | 501| `md` | 12 | 502| `lg` | 16 | 503| `xl` | 20 | 504| `_2xl` | 24 | 505| `_3xl` | 28 | 506| `_4xl` | 32 | 507| `_5xl` | 40 | 508 509### `tokens.fontSize` 510 511| Key | Value | 512|-----|-------| 513| `_2xs` | 9.4 | 514| `xs` | 11.3 | 515| `sm` | 13.1 | 516| `md` | 15 | 517| `lg` | 16.9 | 518| `xl` | 18.8 | 519| `_2xl` | 20.6 | 520| `_3xl` | 24.3 | 521| `_4xl` | 30 | 522| `_5xl` | 37.5 | 523 524### `tokens.lineHeight` 525 526| Key | Value | 527|-----|-------| 528| `tight` | 1.15 | 529| `snug` | 1.3 | 530| `relaxed` | 1.5 | 531 532### `tokens.borderRadius` 533 534| Key | Value | 535|-----|-------| 536| `_2xs` | 2 | 537| `xs` | 4 | 538| `sm` | 8 | 539| `md` | 12 | 540| `lg` | 16 | 541| `xl` | 20 | 542| `full` | 999 | 543 544### `tokens.fontWeight` 545 546| Key | Value | 547|-----|-------| 548| `normal` | `'400'` | 549| `medium` | `'500'` | 550| `semiBold` | `'600'` | 551| `bold` | `'700'` | 552 553### `tokens.labelerColor` 554 555| Key | Value | 556|-----|-------| 557| `purple` | `rgb(105 0 255)` | 558| `purple_dark` | `rgb(83 0 202)` | 559 560### `tokens.TRACKING` 561 562Letter-spacing constant. Value: `0`. 563 564--- 565 566## Palette 567 568### `Palette` type 569 570Defines all color values for a theme. Four color families, each with shades: 571 572- **`contrast_*`** (neutrals): 0, 25, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950, 975, 1000 573- **`primary_*`** (brand blue): 25, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950, 975 574- **`positive_*`** (green): same scale as primary 575- **`negative_*`** (red): same scale as primary 576 577Plus `white`, `black`, and `like` (pink, `#EC4899`). 578 579All values are CSS color strings. 580 581### `DEFAULT_PALETTE` 582 583The standard light-mode palette. Contrast ranges from `#FFFFFF` (0) to `#000000` (1000). Primary blues range from `#F5F9FF` (25) to `#001533` (975). 584 585### `DEFAULT_SUBDUED_PALETTE` 586 587A softer alternative used by the dim theme. Same structure, lower contrast. The darkest contrast value is `#151D28` instead of pure black. 588 589### `invertPalette(palette)` 590 591Flips **all four** color scales (contrast, primary, positive, negative) for dark mode. Swaps `_0`/`_25` with `_1000`/`_975`, and so on symmetrically. The `_500` center values in each scale remain unchanged. Keeps `white`, `black`, and `like` unchanged. 592 593```typescript 594const darkPalette = invertPalette(DEFAULT_PALETTE) 595``` 596 597Returns: `Palette` 598 599--- 600 601## Themes 602 603### `Theme` type 604 605```typescript 606type Theme = { 607 scheme: ThemeScheme // 'light' | 'dark' 608 name: ThemeName // 'light' | 'dark' | 'dim' 609 palette: Palette 610 atoms: ThemeAtoms 611} 612``` 613 614### `ThemeScheme` 615 616`'light' | 'dark'` 617 618### `ThemeName` 619 620`'light' | 'dark' | 'dim'` 621 622### `themes` 623 624Pre-built theme objects, ready to pass to `Provider`: 625 626```typescript 627themes.light // Light scheme, DEFAULT_PALETTE 628themes.dark // Dark scheme, inverted DEFAULT_PALETTE, 0.4 shadow opacity 629themes.dim // Dark scheme, inverted DEFAULT_SUBDUED_PALETTE, 0.4 shadow opacity 630``` 631 632### `createTheme({ scheme, name, palette, options? })` 633 634Builds a `Theme` from a palette. 635 636| Param | Type | Description | 637|-------|------|-------------| 638| `scheme` | `ThemeScheme` | `'light'` or `'dark'` | 639| `name` | `ThemeName` | `'light'`, `'dark'`, or `'dim'` | 640| `palette` | `Palette` | Color palette to use | 641| `options.shadowOpacity` | `number` | Shadow opacity, defaults to `0.1` | 642 643Returns: `Theme` 644 645### `createThemes({ defaultPalette, subduedPalette })` 646 647Builds all three theme variants at once. Inverts the palettes automatically for dark and dim. 648 649| Param | Type | Description | 650|-------|------|-------------| 651| `defaultPalette` | `Palette` | Used for light and dark themes | 652| `subduedPalette` | `Palette` | Used for the dim theme | 653 654Returns: `{ light: Theme, dark: Theme, dim: Theme }` 655 656--- 657 658## Platform 659 660### Detection booleans 661 662These resolve at build time via platform-split files (`.native.ts` vs `.ts`). 663 664| Export | Web | iOS | Android | 665|--------|-----|-----|---------| 666| `isWeb` | `true` | `false` | `false` | 667| `isNative` | `false` | `true` | `true` | 668| `isIOS` | `false` | `true` | `false` | 669| `isAndroid` | `false` | `false` | `true` | 670| `isFabric` | Runtime check: `Boolean(global?.nativeFabricUIManager)` | 671 672### Platform selectors 673 674Identity functions that return the value on the matching platform and `undefined` everywhere else. 675 676```typescript 677web({ cursor: 'pointer' }) // returns the object on web, undefined on native 678native({ elevation: 4 }) // returns the object on native, undefined on web 679ios({ borderCurve: 'continuous' }) 680android({ elevation: 8 }) 681``` 682 683### `platform(specifics)` 684 685Works like React Native's `Platform.select()`. On web, returns `specifics.web` or `specifics.default`. 686 687```typescript 688platform({ web: 16, default: 12 }) 689``` 690 691> On web, this uses `||` (not `??`), so falsy values like `0` or `""` for `specifics.web` will fall through to `specifics.default`. 692 693--- 694 695## Utils 696 697Imported as a namespace: `import { utils } from '@bsky.app/alf'`. 698 699### `utils.alpha(color, opacity)` 700 701Converts a color string to a transparent variant at the given opacity (0 to 1). 702 703```typescript 704utils.alpha('#FF0000', 0.5) // '#FF000080' 705utils.alpha('rgb(255, 0, 0)', 0.5) // 'rgba(255, 0, 0, 0.5)' 706utils.alpha('hsl(0, 100%, 50%)', 0.5) // 'hsla(0, 100%, 50%, 0.5)' 707``` 708 709Supported formats: `#RGB`, `#RRGGBB`, `rgb()`, `hsl()`. Returns the original color unchanged if the format is not recognized. 710 711### `utils.leading(textStyle)` 712 713Calculates a `lineHeight` value from a text style's `fontSize` and `lineHeight` multiplier. 714 715```typescript 716utils.leading({ fontSize: 15, lineHeight: 1.5 }) 717// Web: { lineHeight: '1.5' } (unitless string) 718// Native: { lineHeight: 23 } (rounded pixel value) 719``` 720 721Defaults to `tokens.lineHeight.snug` when `lineHeight` is missing. On native, also defaults to `tokens.fontSize.sm` when `fontSize` is missing (web does not use `fontSize`). 722 723Returns: `Pick<TextStyle, 'lineHeight'>` 724 725### `utils.select(name, options)` 726 727Theme-aware value selector. Pass a `ThemeName` and an object mapping theme names to values. 728 729```typescript 730utils.select('dark', { 731 light: '#FFFFFF', 732 dark: '#000000', 733 dim: '#1A1A2E', 734}) 735// Returns '#000000' 736``` 737 738The type signature accepts either an exhaustive map of all three theme names, or a partial map with a `default` key. When using `default`, you still need to provide values for every theme name you want to handle — the `default` value only applies when the `name` argument falls outside the known `ThemeName` union (which shouldn't happen in practice). If a theme name is omitted from the options, its value will be `undefined`: 739 740```typescript 741utils.select('dim', { light: 'white', default: 'black' }) 742// Returns undefined — 'dim' matches the switch case but options.dim is not set 743``` 744 745For reliable results, always provide all three theme names: 746 747```typescript 748utils.select('dim', { light: 'white', dark: 'black', dim: 'black' }) 749// Returns 'black' 750``` 751 752### `utils.flatten(style)` 753 754Merges a style array (or nested arrays) into a single object. Filters out falsy values. 755 756```typescript 757utils.flatten([a.flex_row, a.gap_md, false && a.p_lg]) 758// { flexDirection: 'row', gap: 12 } 759``` 760 761On web and native, this delegates to `StyleSheet.flatten`. A custom fallback implementation is provided for other environments where neither `.web.ts` nor `.native.ts` is resolved. 762 763Returns: merged style object 764 765--- 766 767## Types 768 769### `TextStyleProp` 770 771```typescript 772type TextStyleProp = { style?: StyleProp<TextStyle> } 773``` 774 775### `ViewStyleProp` 776 777```typescript 778type ViewStyleProp = { style?: StyleProp<ViewStyle> } 779``` 780 781### `ThemeAtoms` 782 783The type of `t.atoms` (where `t = useTheme()`). Maps each theme atom name to its style object. 784 785| Key group | Keys | 786|-----------|------| 787| Text | `text`, `text_contrast_low`, `text_contrast_medium`, `text_contrast_high`, `text_inverted` | 788| Background | `bg`, `bg_contrast_25`, `bg_contrast_50`, `bg_contrast_100` through `bg_contrast_900`, `bg_contrast_950`, `bg_contrast_975` | 789| Border | `border_contrast_low`, `border_contrast_medium`, `border_contrast_high` | 790| Shadow | `shadow_sm`, `shadow_md`, `shadow_lg` | 791 792### `Palette` 793 794Color value map for a theme. See [Palette](#palette) for the full shape. 795 796### `Theme` 797 798Complete theme object containing scheme, name, palette, and atoms. See [Themes](#themes) for details. 799 800### `ThemeScheme` 801 802`'light' | 'dark'` 803 804### `ThemeName` 805 806`'light' | 'dark' | 'dim'` 807 808--- 809 810## Native Overrides 811 812When running on iOS or Android, these atoms behave differently from their web counterparts: 813 814| Atom | Web | Native | 815|------|-----|--------| 816| `fixed` | `position: 'fixed'` | `position: 'absolute'` | 817| `sticky` | `position: 'sticky'` | Empty object | 818| `overflow_auto` | `overflow: 'auto'` | Empty object | 819| `flex_0` | `flex: '0 0 auto'` | `flex: 0` | 820| `border`, `border_t`, `border_b`, `border_l`, `border_r`, `border_x`, `border_y` | `borderWidth: 1` | `borderWidth: StyleSheet.hairlineWidth` | 821| `curve_circular` | Empty object | iOS: `borderCurve: 'circular'`, Android: `undefined` | 822| `curve_continuous` | Empty object | iOS: `borderCurve: 'continuous'`, Android: `undefined` | 823| `shadow_sm`, `shadow_md`, `shadow_lg` | Empty object | Shadow props with `elevation` (Fabric: empty) | 824| `inline`, `block` | `display: 'inline'` / `'block'` | Empty object | 825| `pointer` | `cursor: 'pointer'` | Empty object |