Skip to main content

Your first markdown view and editor

With the package installed, let's put both components on the screen: EnrichedMarkdownText to display rich text and EnrichedMarkdownTextInput to edit it. By the end of this page you'll have rendered your first Markdown and built a minimal editor with a working toolbar button.

Rendering Markdown​

EnrichedMarkdownText is a read-only component. You give it a Markdown string through the markdown prop and it renders fully native text - no WebView involved.

Loading…

That is all a display needs. The onLinkPress callback fires when the reader taps a link - here we hand the URL off to React Native's Linking so it opens in the browser.

You can restyle every element through the markdownStyle prop - see the Style properties reference for the full list.

note

defaultMarkdownStyle is a docs helper, not part of the library: it gives every example on this site a complete light/dark palette in one line. In your own app, pass your own markdownStyle object or omit the prop entirely to get the built-in defaults.

Building an editor​

EnrichedMarkdownTextInput is the writing side. It's an uncontrolled input. It doesn't hold its value in React state or props, but talks to the native text view directly. That keeps it fast, but it means you drive formatting imperatively through a ref rather than by passing a value down.

Here's a minimal editor with a single toolbar button that toggles bold and reflects whether bold is currently active:

Loading...
import { useRef, useState } from 'react';
import { View, Button, StyleSheet, useColorScheme } from 'react-native';
import {
EnrichedMarkdownTextInput,
type EnrichedMarkdownTextInputInstance,
type StyleState,
} from 'react-native-enriched-markdown';
import { defaultInputColors, defaultInputMarkdownStyle } from './theme';

export default function App() {
const isDark = useColorScheme() === 'dark';
const ref = useRef<EnrichedMarkdownTextInputInstance>(null);
const [state, setState] = useState<StyleState | null>(null);

return (
<View style={styles.container}>
<EnrichedMarkdownTextInput
markdownStyle={defaultInputMarkdownStyle(isDark)}
ref={ref}
placeholder="Type here..."
onChangeState={setState}
style={{ ...styles.input, ...defaultInputColors(isDark) }}
/>
<View style={styles.toolbar}>
<Button
title={state?.bold.isActive ? 'Unbold' : 'Bold'}
color={state?.bold.isActive ? 'green' : 'gray'}
onPress={() => ref.current?.toggleBold()}
/>
</View>
</View>
);
}

const styles = StyleSheet.create({
container: { gap: 12 },
input: { fontSize: 18, padding: 12, minHeight: 96 },
toolbar: { flexDirection: 'row', gap: 8 },
});

Two things are worth calling out, because every editor feature follows the same two patterns:

  1. You apply formatting by calling methods on the ref. The ref is typed as EnrichedMarkdownTextInputInstance. Pressing the button calls ref.current?.toggleBold(), which toggles bold across the current selection - or arms it for the next characters you type when there's no selection.
  2. You read formatting from the onChangeState callback. Its payload has one entry per style (bold, italic, and so on), each with an isActive flag. That's what lets the button know whether to say "Bold" or "Unbold" and change color.

The same toggle / onChangeState pair drives italic, underline, strikethrough and more - so extending the toolbar is mostly repeating this shape. The input also ships a native format bar on selection out of the box, so basic formatting works even before you build any UI. See the EnrichedMarkdownTextInput reference for the full set of methods and events.

How the two fit together​

The mental model to carry into the rest of the docs: Markdown is the shared source of truth between the two components.

  • EnrichedMarkdownTextInput edits Markdown. Seed it with a Markdown string (defaultValue) and read the edited content back as Markdown at any time (onChangeMarkdown, or getMarkdown() on the ref - it returns a Promise<string>).
  • EnrichedMarkdownText renders Markdown through its markdown prop.

Because both sides speak the same format, content flows between them without conversion - edit in one, store the Markdown, render it in the other.

note

onChangeMarkdown serializes on every change, which isn't free. If you don't need the Markdown string, leave the callback off and the input skips serialization entirely.

Next steps​

You now have a working display and editor and the model that ties them together. From here: