Eight New Primitives in rn-markdown-editor — Layout, Typography, and Utility Components
Previously:
- Launching the rn-markdown-editor ecosystem
- Five themeable UI primitives — Badge, Button, Icons, Input, Skeleton
- Theming in rn-markdown-editor: How It's Built and How to Use It
- Three Utility Hooks — useMergeState, useDebouncedInput, and useDisclosure
- What's Next for rn-markdown-editor — New Components Coming in v1.0.21
- Five New UI Primitives in rn-markdown-editor v1.0.22 — DropdownMenu, Accordion, Avatar, Card, and Radio
Version v1.0.23 is now live on npm. This release shifts focus from interactive components to the foundational layer underneath them — layout primitives, typography, form controls, and utility components that reduce repetition across your entire app. Eight new additions shipped in this version. Here's a detailed breakdown of each one.
Checkmark
A standalone checkbox indicator component — not a full form field, but the visual primitive that a checkbox field is built from. This separation matters: you can drop Checkmark into a list row, a permission toggle, or a custom form control without coupling it to any specific input management pattern.
How it works internally:
The component supports three visual states: unchecked, checked, and indeterminate. Both the check glyph and the indeterminate dash are rendered as inline SVG via react-native-svg, sized proportionally at size * 0.6 and size * 0.55 respectively so the glyph scales cleanly at any dimension without pixel artifacts.
All color and geometry computations — background, border color, border width, border radius — are consolidated inside a single useMemo block that depends only on the props and theme colors. This means a single dependency array covers all the visual math, and the container style is derived from that result in a second useMemo, so neither recalculates unless something relevant actually changes.
The active state collapses the border (borderWidth: 0) and fills the background, while the inactive state shows only the border ring with a transparent fill. Disabled state replaces the active color with colors.muted in both the background and border, giving a clear visual signal without hiding the component.
Accessibility is handled natively: accessibilityRole="checkbox" and accessibilityState={{ checked, disabled }} are set directly, with indeterminate mapped to the "mixed" checked value that screen readers expect.
// Unchecked
<Checkmark checked={false} size={20} />
// Checked
<Checkmark checked={true} size={20} />
// Indeterminate (e.g., "select all" with partial selection)
<Checkmark indeterminate={true} size={20} />
// Disabled
<Checkmark checked={true} disabled size={20} />
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | false | Checked state |
indeterminate | boolean | false | Renders dash glyph, overrides checked |
disabled | boolean | false | Applies muted color, removes interaction |
size | number | 20 | Width and height in pixels |
radius | number | size * 0.25 | Border radius override |
activeColor | string | colors.foreground | Fill color when active |
borderColor | string | colors.border | Border color when inactive |
glyphColor | string | colors.background | Check/dash glyph color |
disabledColor | string | colors.muted | Color applied in disabled state |
Circle
A layout primitive that renders a circular View. Small in scope, but something you end up writing by hand in a dozen places — notification badges, avatar rings, status dots, icon containers.
How it works internally:
The radius is derived from Math.max(width, height) / 2, which means non-square dimensions still produce a pill shape rather than an oval. The style object is memoized and merged with any passed style prop. All standard ViewProps pass through, so you can attach onPress, testID, or any other native prop directly.
// Status dot
<Circle width={10} height={10} bg={colors.success} />
// Icon container
<Circle width={40} height={40} bg={colors.secondary}>
<Icons.Star size={20} color={colors.foreground} />
</Circle>
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
width | number | 16 | Width in pixels |
height | number | 16 | Height in pixels |
bg | string | "#fff" | Background color |
style | ViewStyle | — | Additional style overrides |
Container
A centered, max-width layout wrapper driven by named size tokens from the active theme. Stops you from hardcoding breakpoint widths in individual screens and keeps layout decisions in one place.
How it works internally:
Container reads container.containerSizes from useTheme() and looks up the value for the size prop key. The resolved maxWidth is applied alongside width: "100%" and alignSelf: "center", so the container stays full-width on small screens and caps out at the token value on wider ones. Changing the theme's container token map updates every Container instance at once.
<Container size="md">
<Heading type="h1">Page Title</Heading>
<Text>Content constrained to the md breakpoint max-width.</Text>
</Container>
Props:
| Prop | Type | Description | |||||
|---|---|---|---|---|---|---|---|
size | `"xs" \ | "sm" \ | "md" \ | "lg" \ | "xl" \ | "2xl"` | Named width token |
style | ViewStyle | Additional style overrides |
Flex
A single-line horizontal row with a built-in gap prop. Eliminates the pattern of writing flexDirection: "row" and a gap value on a plain View every time you need to lay items out horizontally.
How it works internally:
The style is a useMemo over gap (defaulting to 8) merged with any passed style. Everything else passes through as standard ViewProps. The implementation is intentionally minimal — Flex is a convenience wrapper, not a full flexbox abstraction.
<Flex gap={12}>
<Avatar name="Suraj Adhikari" size={32} />
<Heading type="h4">Suraj Adhikari</Heading>
</Flex>
<Flex gap={4}>
<Icons.Heart size={16} color={colors.destructive} />
<Text>142 likes</Text>
</Flex>
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
gap | number | 8 | Horizontal gap between children |
style | ViewStyle | — | Additional style overrides |
Float
A floating badge component — a small labeled circle absolutely positioned at the top-right of its parent. The canonical use case is a notification count on a tab icon or avatar.
How it works internally:
Float composes Circle with an absolute position style (top: 0, right: 0) and renders a Text child inside it. The circle background defaults to colors.destructive (red) but accepts a background override. Text color defaults to colors.foreground and accepts a textColor override. Font size is fixed at 10 to stay readable inside small badge circles.
Since Float is meant to be used inside a View with position: "relative", no additional wrapper is needed on the parent — just make sure the parent has a defined size.
<View style={{ position: "relative" }}>
<Icons.Bell size={24} color={colors.foreground} />
<Float height={16} width={16}>3</Float>
</View>
// Custom color
<Float height={18} width={18} background={colors.primary} textColor="#fff">
9+
</Float>
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
height | number | — | Badge height |
width | number | — | Badge width |
background | string | colors.destructive | Badge background color |
textColor | string | colors.foreground | Label text color |
textStyle | TextProps | — | Additional text style |
For
A declarative list-rendering component — JSX-native .map() with a typed render callback and a built-in empty state fallback. Replaces the pattern of inline array.map(...) calls that litter render functions with inconsistent key handling.
How it works internally:
For is generic over T, so the children render callback is fully typed based on whatever array you pass to each. The empty/undefined guard runs before the map, returning fallback ?? null when each is empty. The component is wrapped with React.memo via a cast that preserves the generic signature — React.memo strips generics by default, so the cast is necessary to keep the callback typed correctly at the call site.
// Primitive array
<For each={["Reply", "Repost", "Upvote"]}>
{(action, index) => <Button key={index} label={action} />}
</For>
// Object array — item is fully typed
<For
each={posts}
fallback={<Text>No posts yet.</Text>}
>
{(post, index) => (
<Card key={post.id}>
<CardHeader>
<CardTitle>{post.title}</CardTitle>
</CardHeader>
</Card>
)}
</For>
// Nested iteration
<For each={users}>
{(user, i) => (
<View key={i}>
<Text>{user.name}</Text>
<For each={user.tags}>
{(tag, j) => <Badge key={j} label={tag} />}
</For>
</View>
)}
</For>
Props:
| Prop | Type | Description |
|---|---|---|
each | T[] | Array to iterate over |
children | (item: T, index: number) => ReactNode | Typed render callback |
fallback | ReactNode | Rendered when array is empty |
Heading
A typed typography component for semantic headings — h1 through h5 — where font size and weight are resolved from the theme's typography token map rather than being hardcoded at the call site.
How it works internally:
Heading reads typography.headingSizes[type] from useTheme() and falls back to h6 sizes if the type isn't found, making it safe even if the token map is partially defined. Font color comes from colors.foreground. Both the heading style lookup and the final merged TextStyle are memoized, so re-renders that don't change type, style, or theme tokens are cheap. All standard TextProps pass through.
<Heading type="h1">Welcome Back</Heading>
<Heading type="h2">Recent Posts</Heading>
<Heading type="h4" style={{ marginBottom: 4 }}>
Section Title
</Heading>
Props:
| Prop | Type | Description | ||||
|---|---|---|---|---|---|---|
type | `"h1" \ | "h2" \ | "h3" \ | "h4" \ | "h5"` | Heading level, resolves size and weight from theme |
style | TextStyle | Additional style overrides |
Highlight
A text component that highlights matched substrings within a string, useful for search results, mention rendering, and keyword callouts.
How it works internally:
The component accepts a query array of strings and builds a single regex from them — escaped, joined with |, and optionally wrapped in word boundary markers (\b) when exactMatch is true. The regex is memoized on query, ignoreCases, and exactMatch so it isn't rebuilt on every render.
The string is split on the regex, which preserves the matched parts as separate array entries (because the regex uses a capture group). Each part is classified as a match or not by checking against a lowercased query array when ignoreCases is on. Matched parts render as HighlightSpan (a Text with backgroundColor and borderRadius: 4); unmatched parts render as plain PlainSpan. Both inner components are memoized independently so re-renders caused by parent state changes don't cascade through the full part list.
// Search result highlighting
<Highlight
query={["markdown", "editor"]}
highlightColor={colors.flameon}
ignoreCases
>
The rn-markdown-editor is a fully themeable markdown editor for React Native.
</Highlight>
// Exact word match only
<Highlight
query={["React"]}
highlightColor="#FFD700"
exactMatch
ignoreCases={false}
>
React Native differs from ReactDOM in its rendering target.
</Highlight>
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
query | string[] | — | Terms to highlight |
highlightColor | string | — | Background color for matched spans |
ignoreCases | boolean | true | Case-insensitive matching |
exactMatch | boolean | false | Word boundary matching |
highlightStyle | TextStyle | — | Additional style for matched spans |
Installation
All eight components are available now in v1.0.23:
npm install rn-markdown-editor
or
yarn add rn-markdown-editor
More components are in active development and will be covered in the next release post as soon as they land.
System Reliability & Support
The development team remains committed to maintaining a secure, efficient, and highly optimized open-source toolkit. Continuous updates are actively deployed to ensure total cross-platform stability across iOS, Android, and Web deployments.
For technical assistance or to report issues, please submit on the comments or our official NPM Project Page.
Thanks for your time and support. Let's make Steem great, again.
Official NPM Link: https://www.npmjs.com/package/rn-markdown-editor
Primary Architecture: TypeScript / JavaScript
Supported Environments: Expo & Bare React Native CLI
Utility Exports: useDebouncedInput, useDisclosure, useMergeState