diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index 1f4fa07..7e3d609 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -35,6 +35,7 @@ export default defineConfig({
text: 'Bridge components',
items: [
{ text: 'Overview', link: '/components/overview' },
+ { text: 'Alert', link: '/components/alert' },
{ text: 'Button', link: '/components/button' },
],
},
diff --git a/docs/components/alert.md b/docs/components/alert.md
new file mode 100644
index 0000000..591b55c
--- /dev/null
+++ b/docs/components/alert.md
@@ -0,0 +1,157 @@
+# Alert
+
+A **native confirmation dialog** put in front of an action. The web side asks for
+the alert and hears back only if it was confirmed; iOS presents a
+`UIAlertController` and Android an `AlertDialog`.
+
+Outside of Hotwire Native nothing is registered and the hook falls back to
+`window.confirm`, so the same page still works in a regular browser.
+
+
+
+*Confirming reports back and the page acts on it. Dismissing reports nothing at
+all — the page is left exactly as it was.*
+
+::: tip You copy it, you own it
+There is nothing to install. The files below are the complete component — paste
+the web one plus whichever platforms you ship into your app, and change them
+however you like. They are shown straight from the
+[hotwire-bridge-components](https://github.com/zumkorn/hotwire-bridge-components/tree/main/registry/alert)
+registry, so what you see here is what the registry holds.
+:::
+
+## Web side
+
+Save this as `bridge/useBridgeAlert.tsx` in your app:
+
+::: code-group
+
+<<< @/../vendor/hotwire-bridge-components/registry/alert/inertia/react.tsx [useBridgeAlert.tsx]
+
+:::
+
+Then call `show` from wherever the action starts. It runs `onConfirm` only if the
+alert was confirmed:
+
+```jsx
+import { useBridgeAlert } from '@/bridge/useBridgeAlert'
+
+function DeleteButton({ file }) {
+ const { show } = useBridgeAlert()
+
+ const destroy = () => {
+ show(
+ {
+ title: 'Delete this file?',
+ description: 'This cannot be undone.',
+ destructive: true,
+ confirm: 'Delete',
+ },
+ () => router.delete(`/files/${file.id}`)
+ )
+ }
+
+ return
+}
+```
+
+| Option | Type | Default | Purpose |
+| --- | --- | --- | --- |
+| `title` | `string` | — | Headline of the alert |
+| `description` | `string` | — | Body text under the title |
+| `destructive` | `boolean` | `false` | Draw the confirming action as destructive |
+| `confirm` | `string` | `'OK'` | Label of the confirming action |
+| `dismiss` | `string` | `'Cancel'` | Label of the dismissing action |
+
+`useBridgeAlert()` also returns `supported`, for a page that would rather render
+its own dialog than let the browser fallback handle it.
+
+::: tip A hook, not a component
+[Button](/components/button) draws native UI the moment it mounts, so it is a
+component whose children are the web fallback. This one draws nothing until it is
+called — there is no markup for a component to own, so it ships as a hook.
+:::
+
+## iOS side
+
+Add this file to your Xcode project:
+
+::: code-group
+
+<<< @/../vendor/hotwire-bridge-components/registry/alert/native/AlertComponent.swift [AlertComponent.swift]
+
+:::
+
+Then register it at launch, in `AppDelegate`:
+
+```swift
+Hotwire.registerBridgeComponents([
+ AlertComponent.self,
+ // … your other components
+])
+```
+
+Until it is registered, `supported` stays false on the web side and `show` falls
+back to `window.confirm`.
+
+## The contract
+
+Component name: `alert`.
+
+### `show` — web → native
+
+Presents the alert. Sent once per confirmation, not on connect.
+
+```jsonc
+{
+ "title": "Are you sure?", // string, required — headline
+ "description": "This cannot be undone.", // string, optional — body text
+ "destructive": true, // bool, optional, default false
+ "confirm": "Delete", // string, optional, default "OK"
+ "dismiss": "Cancel" // string, optional, default "Cancel"
+}
+```
+
+### `show` reply — native → web
+
+Native replies to the `show` message **only when the confirming action is
+tapped**. Dismissing the alert sends nothing at all. The reply carries no data —
+it is the confirmation signal itself.
+
+::: warning A dismissal is silence
+Because nothing comes back from a cancelled alert, its callback is never invoked
+and stays in the bridge's map until the page unmounts. The registry hook drops
+the previous callback before each `show`, so at most one is ever outstanding. If
+you write your own web side, do the same — see
+[Callback lifetime](/components/overview#callback-lifetime).
+:::
+
+## Android
+
+The Kotlin half presents an `AlertDialog`. Add this file to your project:
+
+::: code-group
+
+<<< @/../vendor/hotwire-bridge-components/registry/alert/native/AlertComponent.kt [AlertComponent.kt]
+
+:::
+
+Then register it at launch, in your `Application`:
+
+```kotlin
+Hotwire.registerBridgeComponents(
+ BridgeComponentFactory("alert", ::AlertComponent),
+ // … your other components
+)
+```
+
+
+
+*The same page on Android: dismissing with `Keep` leaves the page alone,
+confirming with `Delete` updates it.*
+
+::: warning No destructive style
+Android has no destructive button style, so `destructive` tints the positive
+button with the theme's `colorError` instead. It reads as a warning rather than
+as the platform-standard destructive action iOS gives you.
+:::
diff --git a/docs/public/media/alert-android.mp4 b/docs/public/media/alert-android.mp4
new file mode 100644
index 0000000..833bceb
Binary files /dev/null and b/docs/public/media/alert-android.mp4 differ
diff --git a/docs/public/media/alert-ios.mp4 b/docs/public/media/alert-ios.mp4
new file mode 100644
index 0000000..be63b57
Binary files /dev/null and b/docs/public/media/alert-ios.mp4 differ
diff --git a/vendor/hotwire-bridge-components b/vendor/hotwire-bridge-components
index d7c3a3f..8b6d14c 160000
--- a/vendor/hotwire-bridge-components
+++ b/vendor/hotwire-bridge-components
@@ -1 +1 @@
-Subproject commit d7c3a3f5924df853a25fa71bc162c257d6186002
+Subproject commit 8b6d14cebadc721efc3400795c08583c2fd89eff