https://github.com/densermeerkat/hyphen
⌨️ WYSIWYG markdown editor library for Compose Multiplatform. Live formatting, keyboard shortcuts, clipboard, and undo/redo history.
https://github.com/densermeerkat/hyphen
android compose-multiplatform desktop jetpack-compose kmp kotlin kotlin-multiplatform markdown markdown-editor rich-text-editor wasmjs wysiwyg
Last synced: about 1 month ago
JSON representation
⌨️ WYSIWYG markdown editor library for Compose Multiplatform. Live formatting, keyboard shortcuts, clipboard, and undo/redo history.
- Host: GitHub
- URL: https://github.com/densermeerkat/hyphen
- Owner: DenserMeerkat
- License: apache-2.0
- Created: 2026-02-28T16:43:49.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-05-20T17:14:13.000Z (about 2 months ago)
- Last Synced: 2026-05-24T09:06:04.663Z (about 1 month ago)
- Topics: android, compose-multiplatform, desktop, jetpack-compose, kmp, kotlin, kotlin-multiplatform, markdown, markdown-editor, rich-text-editor, wasmjs, wysiwyg
- Language: Kotlin
- Homepage: https://densermeerkat.github.io/hyphen/
- Size: 12.2 MB
- Stars: 53
- Watchers: 4
- Forks: 1
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Hyphen

A WYSIWYG Markdown editor for Compose Multiplatform.
Type in Markdown, see formatting live. Copy as Markdown. Works on Android, Desktop, and Web.

## Features
### ✍️ Live Markdown Input
Type Markdown syntax directly and watch it convert as you write — no mode switching, no preview pane required.
| Syntax | Style |
|-------------------------|----------------------|
| `**text**` | **Bold** |
| `*text*` | _Italic_ |
| `__text__` | Underline |
| `[text](url)` | [Link](url) |
| `` `text` `` | `Inline code` |
| `~~text~~` | ~~Strikethrough~~ |
| `==text==` | Highlight |
| `# ` at line start | Heading 1 |
| `## ` at line start | Heading 2 |
| `### ` at line start | Heading 3 |
| `#### ` at line start | Heading 4 |
| `##### ` at line start | Heading 5 |
| `###### ` at line start | Heading 6 |
| `- ` at line start | Bullet list |
| `1. ` at line start | Ordered list |
| `> ` at line start | Blockquote |
| `- [ ] ` at line start | Checkbox (unchecked) |
| `- [x] ` at line start | Checkbox (checked) |
| `[text](scheme:id)` | Mention |
### 📋 Markdown Clipboard
Cut, copy, and paste all work across Android, Desktop, and Web. Copying a selection serializes it to Markdown automatically, paste into any Markdown-aware editor and all formatting travels with it.
When you need to write something else to the clipboard from inside the editor (e.g. a raw URL from a link context menu), use [`LocalHyphenRawClipboard`](#bypassing-markdown-clipboard-serialization) to reach the underlying system clipboard directly.
### ⌨️ Keyboard Shortcuts
Full hardware keyboard support on Desktop and Web:
| Shortcut | Action |
|--------------------------|---------------------------------|
| `Ctrl / Cmd + B` | Toggle bold |
| `Ctrl / Cmd + I` | Toggle italic |
| `Ctrl / Cmd + U` | Toggle underline |
| `Ctrl / Cmd + Shift + S` | Toggle strikethrough |
| `Ctrl / Cmd + Shift + X` | Toggle strikethrough |
| `Ctrl / Cmd + Alt + X` | Toggle strikethrough |
| `Ctrl / Cmd + Shift + H` | Toggle highlight |
| `Ctrl / Cmd + Space` | Clear all styles on selection |
| `Ctrl / Cmd + 1` | Toggle Heading 1 |
| `Ctrl / Cmd + 2` | Toggle Heading 2 |
| `Ctrl / Cmd + 3` | Toggle Heading 3 |
| `Ctrl / Cmd + 4` | Toggle Heading 4 |
| `Ctrl / Cmd + 5` | Toggle Heading 5 |
| `Ctrl / Cmd + 6` | Toggle Heading 6 |
| `Ctrl / Cmd + Enter` | Toggle checkbox on current line |
| `Ctrl / Cmd + K` | Toggle link on selection |
| `Ctrl / Cmd + Z` | Undo |
| `Ctrl / Cmd + Y` | Redo |
| `Ctrl / Cmd + Shift + Z` | Redo |
### ↩️ Undo / Redo History
Granular history with snapshots saved at word boundaries, pastes, and Markdown conversions. The redo stack is maintained correctly across all operations, including toolbar toggles and programmatic edits.
### 🌍 Compose Multiplatform
Single shared implementation targeting Android, Desktop (JVM), and Web (WasmJS / JS).
### 🏷️ Mentions & Autocomplete
Powerful trigger-based autocomplete and interaction system. Define triggers like `@` or `#`, show custom suggestion menus, and handle mention-specific hover cards or context menus.
---
## Installation
### Using `libs.versions.toml` (recommended)
Add the version and library entry to your version catalog:
**`gradle/libs.versions.toml`**
```toml
[versions]
hyphen = "0.5.0-alpha04"
[libraries]
hyphen = { group = "io.github.densermeerkat", name = "hyphen", version.ref = "hyphen" }
```
Then reference it in your shared module:
**`shared/build.gradle.kts`**
```kotlin
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.hyphen)
}
}
}
```
> `commonMain` is the source set that compiles for every target at once — Android, Desktop, and Web. Declaring Hyphen there means you write the dependency once and all platforms pick it up automatically.
### Using string notation
```kotlin
// shared/build.gradle.kts
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.densermeerkat:hyphen:0.5.0-alpha04")
}
}
}
```
## Quick Start
```kotlin
val state = rememberHyphenTextState(
initialText = "**Hello**, *Hyphen*!"
)
HyphenTextField(
state = state,
label = { Text("Notes") },
)
// Read the result at any time
val markdown = state.toMarkdown()
```
---
## Choosing an Editor Component
Hyphen ships two editor composables. Use whichever fits your design:
### `HyphenBasicTextEditor`
A thin wrapper around `BasicTextField` with no decoration. Use this when you control the layout yourself or want full design freedom.
```kotlin
HyphenBasicTextEditor(
state = state,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
textStyle = TextStyle(
fontSize = 16.sp,
color = MaterialTheme.colorScheme.onSurface,
),
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
onMarkdownChange = { markdown -> /* sync to ViewModel */ },
)
```
### `HyphenTextField` _(Material 3)_
Wraps `HyphenBasicTextEditor` inside a standard Material3 filled text field decorator — labels, placeholder, leading/trailing icons, supporting text, and error state all work out of the box.
```kotlin
HyphenTextField(
state = state,
label = { Text("Notes") },
placeholder = { Text("Start typing…") },
trailingIcon = { Icon(Icons.Default.Edit, contentDescription = null) },
supportingText = { Text("Markdown supported") },
modifier = Modifier.fillMaxWidth(),
)
```
Both composables accept the same `styleConfig`, `mentionConfig`, `triggerPopup`, `onTextChange`, `onMarkdownChange`, and `clipboardLabel` parameters. The Material3 variant additionally accepts `colors`, `shape`, `labelPosition`, `contentPadding`, and all standard decoration slots.
---
## Usage
### Toolbar buttons — keeping focus on Desktop & Web
On Desktop and Web, clicking a button moves keyboard focus away from the editor. This causes the text selection to be lost before the style toggle runs. Fix this by adding `focusProperties { canFocus = false }` to every toolbar button so focus never leaves the editor when a button is tapped:
```kotlin
IconToggleButton(
checked = state.hasStyle(MarkupStyle.Bold),
onCheckedChange = { state.toggleStyle(MarkupStyle.Bold) },
modifier = Modifier.focusProperties { canFocus = false }, // ← required on Desktop & Web
) {
Icon(Icons.Default.FormatBold, contentDescription = "Bold")
}
```
This applies to any clickable element in your toolbar — `IconButton`, `Button`, `IconToggleButton`, etc.
### Custom style config
```kotlin
HyphenBasicTextEditor(
state = state,
styleConfig = HyphenStyleConfig(
boldStyle = SpanStyle(
fontWeight = FontWeight.ExtraBold,
color = Color(0xFF1A73E8),
),
highlightStyle = SpanStyle(
background = Color(0xFFFFF176),
),
inlineCodeStyle = SpanStyle(
background = Color(0xFFF1F3F4),
fontFamily = FontFamily.Monospace,
color = Color(0xFFD93025),
),
),
)
```
### Programmatic control
```kotlin
// Load new Markdown content (resets undo history)
state.setMarkdown("# New content\n\nHello!")
// Toggle formatting from a custom button
Button(onClick = { state.toggleStyle(MarkupStyle.Bold) }) { Text("B") }
// Remove all inline formatting from the current selection
Button(onClick = { state.clearAllStyles() }) { Text("Clear") }
// Undo / redo
state.undo()
state.redo()
```
### Mentions & Autocomplete
Hyphen provides a powerful trigger-based autocomplete and interaction framework for mentions (such as @users), hashtags (such as #features), or custom template variables.
#### 1. Define Triggers
Configure the triggers and the Markdown schemes they map to. Triggers are completely optional and can be defined at initialization:
```kotlin
val triggers = listOf(
TriggerConfig(trigger = "@", scheme = "user"),
TriggerConfig(trigger = "#", scheme = "tag")
)
// Triggers can be optionally passed directly to rememberHyphenTextState
val state = rememberHyphenTextState(triggerConfigs = triggers)
```
#### 2. Configure Interactions
Use `mentionConfig` (`HyphenMentionConfig`) to manage interactions with completed mentions, such as handling taps, presenting hover cards, or showing custom context Dropdown menus:
```kotlin
HyphenTextField(
state = state,
mentionConfig = HyphenMentionConfig(
onMentionClick = { mention ->
println("Clicked mention: ${mention.display} with ID: ${mention.id}")
},
hoverCardContent = { mention ->
Surface(tonalElevation = 8.dp, shape = MaterialTheme.shapes.medium) {
Text("Viewing details for ${mention.display}", Modifier.padding(8.dp))
}
},
dropdownContent = { span, offset, onDismiss ->
// Display a custom context menu on right-click / long-press
val mention = span.style as? MarkupStyle.Mention
if (mention != null) {
DropdownMenu(expanded = true, onDismissRequest = onDismiss, offset = offset) {
DropdownMenuItem(
text = { Text("Profile") },
onClick = {
println("Opening profile for ${mention.id}")
onDismiss()
}
)
}
}
}
),
triggerPopup = { triggerState ->
// Render your custom autocomplete suggestions menu
MySuggestionMenu(
query = triggerState.query,
onSelected = { item ->
state.completeMention(id = item.id, display = item.display, trigger = triggerState)
}
)
}
)
```
#### 3. Keyboard Navigation & Auto-Completion
When an autocomplete trigger is active, the editor automatically intercepts and routes arrow keys (`Up`/`Down`) and the `Enter` key to coordinate suggestion selection.
* Watch `state.suggestionSelectedIndex`, `state.suggestionCount`, and `state.suggestionSelectionRequested` to control your suggestions popup.
* Call `state.completeMention(id, display)` to insert a completed mention, which replaces the active query and registers a `MarkupStyle.Mention` style range.
* Use `state.insertMention(display, scheme, id)` to programmatically insert a formatted mention at the current cursor index.
> [!TIP]
> Hyphen includes a built-in `TriggerSuggestions` composable that handles standard Material3 list styling and keyboard selection synchronization for you out-of-the-box. Check the sample project for a full demonstration.
### Reactive observation
```kotlin
// Callback — fires on every text or formatting change
HyphenBasicTextEditor(
state = state,
onMarkdownChange = { markdown -> viewModel.onContentChanged(markdown) },
)
// Flow — collect anywhere, debounce freely
viewModelScope.launch {
state.markdownFlow
.debounce(500)
.collect { markdown -> repository.save(markdown) }
}
```
### Bypassing Markdown clipboard serialization
Hyphen intercepts every `Clipboard.setClipEntry()` call made from within the editor. When there is an active selection it replaces whatever you wrote with the Markdown-serialized form of that selection. This is the right behaviour for a user pressing **Ctrl+C**, but not for a programmatic action like "Copy link URL" that is triggered from a context menu while the link text happens to be selected.
Use `LocalHyphenRawClipboard` to access the original, unintercepted system clipboard:
```kotlin
import com.denser.hyphen.ui.LocalHyphenRawClipboard
@Composable
fun LinkContextMenu(url: String, onDismiss: () -> Unit) {
// Always falls through to the real system clipboard, ignoring any editor selection
val rawClipboard = LocalHyphenRawClipboard.current
val scope = rememberCoroutineScope()
DropdownMenuItem(
text = { Text("Copy URL") },
onClick = {
scope.launch {
rawClipboard?.setClipEntry(
ClipEntry(ClipData.newPlainText("URL", url))
)
}
onDismiss()
}
)
}
```
`LocalHyphenRawClipboard.current` is `null` when the composable is rendered outside a `HyphenBasicTextEditor`. Fall back to `LocalClipboard.current` if you need to handle both cases:
```kotlin
val clipboard = LocalHyphenRawClipboard.current ?: LocalClipboard.current
```
> [!IMPORTANT]
> On **Android**, `LocalHyphenRawClipboard` is **essential** for any custom clipboard write originating from inside the editor. Android has no alternative clipboard path — all `setClipEntry()` calls go through `LocalClipboard`, which Hyphen intercepts. If the editor has an active selection at the time (e.g. because a long-press on a link both selects it and opens a context menu), your custom value will be silently replaced with the Markdown-serialized selection unless you use `LocalHyphenRawClipboard`.
>
> On Desktop and Web the interception follows the same pattern but there are additional native clipboard APIs available as fallbacks.
---
## API Reference
### `HyphenBasicTextEditor`
| Parameter | Type | Default | Description |
|:--------------------|:----------------------------|:------------------------------|:------------------------------------------------------------------------------------------------|
| `state` | `HyphenTextState` | **Required** | Holds text content, spans, selection, and undo/redo history. |
| `modifier` | `Modifier` | `Modifier` | Applied to the underlying `BasicTextField`. |
| `enabled` | `Boolean` | `true` | When `false`, the field is neither editable nor focusable. |
| `readOnly` | `Boolean` | `false` | When `true`, the field cannot be modified but can be focused and copied. |
| `textStyle` | `TextStyle` | `TextStyle(fontSize = 16.sp)` | Typographic style applied to the visible text. |
| `styleConfig` | `HyphenStyleConfig` | `HyphenStyleConfig()` | Visual appearance of each `MarkupStyle`. |
| `linkConfig` | `HyphenLinkConfig` | `HyphenLinkConfig()` | Interaction configuration for link spans (menus, dialogs, opening URLs). |
| `mentionConfig` | `HyphenMentionConfig` | `HyphenMentionConfig()` | Interaction configuration for mention spans (hover cards, dropdowns, click handlers). |
| `triggerPopup` | `@Composable (TriggerState) -> Unit` | `{}` | Composable content shown in a popup when a trigger is active. |
| `keyboardOptions` | `KeyboardOptions` | Sentences, no autocorrect | Software keyboard options. |
| `lineLimits` | `TextFieldLineLimits` | `TextFieldLineLimits.Default` | Single-line or multi-line behaviour. |
| `scrollState` | `ScrollState` | `rememberScrollState()` | Controls vertical or horizontal scroll of the field content. |
| `interactionSource` | `MutableInteractionSource?` | `null` | Hoist to observe focus, hover, and press interactions externally. |
| `cursorBrush` | `Brush` | `SolidColor(Color.Black)` | Brush used to paint the cursor. |
| `decorator` | `TextFieldDecorator?` | `null` | Optional decorator for external visual styling. |
| `onTextLayout` | `(Density.(...) -> Unit)?` | `null` | Callback invoked on every text layout recalculation. |
| `clipboardLabel` | `String` | `"Markdown Text"` | Label attached to clipboard entries on copy/cut. |
| `onTextChange` | `((String) -> Unit)?` | `null` | Callback invoked whenever the plain text changes. |
| `onMarkdownChange` | `((String) -> Unit)?` | `null` | Callback invoked whenever text or formatting changes, providing the serialized Markdown string. |
### `HyphenTextState`
| Property / Method | Type / Return | Description |
|:------------------------------|:----------------------------|:-------------------------------------------------------------------------------------------|
| `textFieldState` | `TextFieldState` | The underlying Compose foundation state driving the editor. |
| `text` | `String` | Plain text with all Markdown syntax stripped. |
| `selection` | `TextRange` | Current cursor position or selected range. |
| `spans` | `List` | Snapshot-observable list of active formatting spans. |
| `pendingOverrides` | `Map` | Transient style intent applied to the next typed characters. |
| `canUndo` / `canRedo` | `Boolean` | `true` if undo/redo actions are available in the history stack. |
| `activeLinkForEditing` | `MarkupStyleRange?` | The link span currently being edited via the built-in dialog. |
| `isFocused` | `Boolean` | Whether the text field currently has input focus. |
| `triggerConfigs` | `List` | Active autocomplete trigger configurations (e.g. `@` or `#`). |
| `activeTrigger` | `TriggerState?` | The current active trigger being typed by the user, if any. |
| `suggestionSelectedIndex` | `Int` | The index of the currently highlighted suggestion in the autocomplete menu. |
| `suggestionCount` | `Int` | Total number of suggestions currently available (must be set by the custom menu). |
| `suggestionSelectionRequested`| `Boolean` | Set to `true` when Enter is pressed on an active trigger to request selection completion. |
| `toggleStyle(style)` | `Unit` | Toggles an inline or block style on the current selection. |
| `toggleCheckbox(index?)` | `Unit` | Toggles the checked/unchecked state of checkboxes in the selection or at a specific index. |
| `clearAllStyles()` | `Unit` | Removes all inline formatting from the selection; suppresses at cursor. |
| `toggleLink()` | `Unit` | Wraps selection in a link, or opens an existing link at the cursor for editing. |
| `updateLink(span, text, url)` | `Unit` | Updates an existing link's display text and URL. |
| `completeMention(id, display, triggerEnd?, trigger?)` | `Unit` | Completes the active trigger, replacing the query with a formatted mention span. |
| `dismissActiveTrigger()` | `Unit` | Clears the currently active trigger state, dismissing suggestions popups. |
| `activateTrigger(config, query)` | `Unit` | Programmatically activates an autocomplete trigger at the current cursor position. |
| `insertText(value)` | `Unit` | Programmatically inserts text at the current cursor, running full Markdown/trigger checks. |
| `insertMention(display, scheme, id)` | `Unit` | Programmatically inserts a formatted mention at the current cursor position. |
| `hasStyle(style)` | `Boolean` | `true` if the style is active at the current selection or cursor. |
| `isStyleAt(index, style)` | `Boolean` | Point query against the span list (ignores selection / overrides). |
| `clearPendingOverrides()` | `Unit` | Resets transient typing intent. |
| `undo()` / `redo()` | `Unit` | Navigates the undo / redo history stack. |
| `toMarkdown(start?, end?)` | `String` | Serializes content (or a substring range) to a Markdown formatted string. |
| `setMarkdown(markdown)` | `Unit` | Replaces all content programmatically, parses it, and resets history. |
| `markdownFlow` | `Flow` | Emits the serialized Markdown string on every text or formatting change. |
### `HyphenStyleConfig`
| Property | Default Value |
|--------------------------|----------------------------------------------------------------------------------------------------|
| `boldStyle` | `SpanStyle(fontWeight = FontWeight.Bold)` |
| `italicStyle` | `SpanStyle(fontStyle = FontStyle.Italic)` |
| `underlineStyle` | `SpanStyle(textDecoration = TextDecoration.Underline)` |
| `strikethroughStyle` | `SpanStyle(textDecoration = TextDecoration.LineThrough)` |
| `highlightStyle` | `SpanStyle(background = Color(0xFFFFEB3B).copy(alpha = 0.4f))` |
| `inlineCodeStyle` | `SpanStyle(background = Color.Gray.copy(alpha = 0.15f), fontFamily = FontFamily.Monospace)` |
| `blockquoteSpanStyle` | `SpanStyle(fontStyle = FontStyle.Italic, color = Color.Gray, background = Color.Gray.copy(0.05f))` |
| `bulletListStyle` | `ListItemStyle()` |
| `orderedListStyle` | `ListItemStyle()` |
| `checkboxCheckedStyle` | `SpanStyle(textDecoration = TextDecoration.LineThrough)` |
| `checkboxUncheckedStyle` | `null` |
| `h1Style` | `SpanStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold)` |
| `h2Style` | `SpanStyle(fontSize = 22.sp, fontWeight = FontWeight.Bold)` |
| `h3Style` | `SpanStyle(fontSize = 20.sp, fontWeight = FontWeight.Bold)` |
| `h4Style` | `SpanStyle(fontSize = 18.sp, fontWeight = FontWeight.Bold)` |
| `h5Style` | `SpanStyle(fontSize = 17.sp, fontWeight = FontWeight.Bold)` |
| `h6Style` | `SpanStyle(fontSize = 16.sp, fontWeight = FontWeight.Bold)` |
| `linkStyle` | `SpanStyle(color = Color.Blue, textDecoration = TextDecoration.Underline)` |
| `mentionStyle` | `SpanStyle(color = Color(0xFF1976D2), fontWeight = FontWeight.Medium)` |
| `mentionStyles` | `emptyMap()` |
### `HyphenMentionConfig`
Interaction configuration for mention spans, controlling what happens when they are clicked, hovered, or right-clicked/long-pressed.
| Property | Type | Default | Description |
|:---|:---|:---|:---|
| `onMentionClick` | `(MarkupStyle.Mention) -> Unit` | `{}` | Callback invoked when a mention span is clicked or tapped. |
| `hoverCardContent` | `@Composable (MarkupStyle.Mention) -> Unit` | `{}` | Composable content displayed in a popup hover card when hovered. |
| `dropdownContent` | `@Composable (span: MarkupStyleRange, menuOffset: Offset, onDismiss: () -> Unit) -> Unit` | `null` | Optional context DropdownMenu composable displayed on right-click or long-press. |
### `TriggerSuggestions`
Built-in Material 3 helper popup for displaying autocomplete items and managing selection index synchronization.
```kotlin
TriggerSuggestions(
state = state,
trigger = triggerState,
items = listOf(
SuggestionItem(id = "alice", display = "Alice"),
SuggestionItem(id = "bob", display = "Bob")
),
onSelect = { item ->
state.completeMention(id = item.id, display = item.display, trigger = triggerState)
}
)
```
#### `SuggestionItem`
| Property | Type | Default | Description |
|:---|:---|:---|:---|
| `id` | `String` | **Required** | The unique identifier of the target entity. |
| `display` | `String` | **Required** | The text displayed in the suggestion list and inserted into the editor. |
| `subtitle` | `String?` | `null` | Optional descriptive label displayed below the main text. |
| `icon` | `@Composable (() -> Unit)?` | `null` | Optional leading composable slot (e.g. user avatar or icon). |
### `MarkupStyle`
```kotlin
// Inline styles
MarkupStyle.Bold
MarkupStyle.Italic
MarkupStyle.Underline
MarkupStyle.Strikethrough
MarkupStyle.InlineCode
MarkupStyle.Highlight
// Heading styles
MarkupStyle.H1
MarkupStyle.H2
MarkupStyle.H3
MarkupStyle.H4
MarkupStyle.H5
MarkupStyle.H6
// Block styles
MarkupStyle.BulletList
MarkupStyle.OrderedList
MarkupStyle.Blockquote
MarkupStyle.CheckboxUnchecked
MarkupStyle.CheckboxChecked
// Mention style
MarkupStyle.Mention(display, scheme, id)
```
### `ListItemStyle`
Controls the prefix marker and content text of a list item independently. Used by `bulletListStyle`
and `orderedListStyle` on `HyphenStyleConfig`.
| Property | Type | Default | Description |
|----------------|--------------|---------|------------------------------------------------------|
| `prefixStyle` | `SpanStyle?` | `null` | Applied to the marker (`-`, `1.`, `- [ ]`, `- [x]`). |
| `contentStyle` | `SpanStyle?` | `null` | Applied to the text after the marker. |
### Checklist Styling
Checkboxes in Hyphen are rendered as native Material3 widgets overlaid on the editor. This ensures
they always match your theme and remain perfectly aligned regardless of font size. Use
`checkboxCheckedStyle` and `checkboxUncheckedStyle` to style the **label text** of the checklist
items:
```kotlin
HyphenBasicTextEditor(
state = state,
styleConfig = HyphenStyleConfig(
checkboxCheckedStyle = SpanStyle(
textDecoration = TextDecoration.LineThrough,
color = Color.Gray,
),
),
)
```
---
## Supported Platforms
| Platform | Status |
|---------------|------------|
| Android | ✅ |
| Desktop (JVM) | ✅ |
| Web (WasmJS) | ✅ |
| Web (JS / IR) | ✅ |
| iOS | 🚧 Planned |