diff --git a/examples/basic-demo/.gitignore b/examples/basic-demo/.gitignore
new file mode 100644
index 0000000..d0d4097
--- /dev/null
+++ b/examples/basic-demo/.gitignore
@@ -0,0 +1,27 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+# Yarn lock (use parent's lock)
+yarn.lock
diff --git a/examples/basic-demo/README.md b/examples/basic-demo/README.md
new file mode 100644
index 0000000..48068e1
--- /dev/null
+++ b/examples/basic-demo/README.md
@@ -0,0 +1,33 @@
+# Basic Demo
+
+A minimal example demonstrating `@syntropy-labs/react-web-speech`.
+
+## Setup
+
+```bash
+# From the root of the repository
+cd examples/basic-demo
+yarn install
+yarn dev
+```
+
+## Features Demonstrated
+
+- `useSpeechInputWithCursor` hook
+- Cursor-aware text insertion
+- Silence timeout
+- Permission state handling
+- Start/Stop toggling
+
+## Note
+
+This example uses `link:../..` to reference the local package. The parent package must be built first:
+
+```bash
+# From root
+yarn build
+
+# Then run the example
+cd examples/basic-demo
+yarn dev
+```
diff --git a/examples/basic-demo/eslint.config.js b/examples/basic-demo/eslint.config.js
new file mode 100644
index 0000000..5e6b472
--- /dev/null
+++ b/examples/basic-demo/eslint.config.js
@@ -0,0 +1,23 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs.flat.recommended,
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ },
+])
diff --git a/examples/basic-demo/index.html b/examples/basic-demo/index.html
new file mode 100644
index 0000000..bdb8ca6
--- /dev/null
+++ b/examples/basic-demo/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ test-speech-app
+
+
+
+
+
+
diff --git a/examples/basic-demo/package.json b/examples/basic-demo/package.json
new file mode 100644
index 0000000..db62255
--- /dev/null
+++ b/examples/basic-demo/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "basic-demo",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@syntropy-labs/react-web-speech": "link:../..",
+ "react": "^19.2.0",
+ "react-dom": "^19.2.0"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.39.1",
+ "@types/node": "^24.10.1",
+ "@types/react": "^19.2.5",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.1",
+ "eslint": "^9.39.1",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.4.24",
+ "globals": "^16.5.0",
+ "typescript": "~5.9.3",
+ "typescript-eslint": "^8.46.4",
+ "vite": "npm:rolldown-vite@7.2.5"
+ },
+ "resolutions": {
+ "vite": "npm:rolldown-vite@7.2.5"
+ }
+}
diff --git a/examples/basic-demo/src/App.tsx b/examples/basic-demo/src/App.tsx
new file mode 100644
index 0000000..c91d38b
--- /dev/null
+++ b/examples/basic-demo/src/App.tsx
@@ -0,0 +1,48 @@
+import { useRef, useState } from 'react'
+import { useSpeechInputWithCursor } from '@syntropy-labs/react-web-speech'
+
+function App() {
+ const [value, setValue] = useState('')
+ const inputRef = useRef(null)
+
+ const { isListening, isSupported, permissionState, toggle, transcript, error } =
+ useSpeechInputWithCursor({
+ inputRef,
+ value,
+ onChange: setValue,
+ appendSpace: true,
+ silenceTimeout: 3000,
+ })
+
+ return (
+
+
🎙️ Speech Input Test
+
+
Supported: {isSupported ? '✅' : '❌'}
+
Permission: {permissionState}
+
Listening: {isListening ? '🔴 Yes' : '⚪ No'}
+ {error &&
Error: {error.message}
}
+
+
+ )
+}
+
+export default App
diff --git a/examples/basic-demo/src/main.tsx b/examples/basic-demo/src/main.tsx
new file mode 100644
index 0000000..a436f98
--- /dev/null
+++ b/examples/basic-demo/src/main.tsx
@@ -0,0 +1,9 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import App from './App.tsx'
+
+createRoot(document.getElementById('root')!).render(
+
+
+
+)
diff --git a/examples/basic-demo/tsconfig.app.json b/examples/basic-demo/tsconfig.app.json
new file mode 100644
index 0000000..a9b5a59
--- /dev/null
+++ b/examples/basic-demo/tsconfig.app.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "types": ["vite/client"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/examples/basic-demo/tsconfig.json b/examples/basic-demo/tsconfig.json
new file mode 100644
index 0000000..d32ff68
--- /dev/null
+++ b/examples/basic-demo/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "files": [],
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
+}
diff --git a/examples/basic-demo/tsconfig.node.json b/examples/basic-demo/tsconfig.node.json
new file mode 100644
index 0000000..8a67f62
--- /dev/null
+++ b/examples/basic-demo/tsconfig.node.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "types": ["node"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/examples/basic-demo/vite.config.ts b/examples/basic-demo/vite.config.ts
new file mode 100644
index 0000000..75db6e0
--- /dev/null
+++ b/examples/basic-demo/vite.config.ts
@@ -0,0 +1,14 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+import path from 'path'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ resolve: {
+ alias: {
+ react: path.resolve('./node_modules/react'),
+ 'react-dom': path.resolve('./node_modules/react-dom'),
+ },
+ },
+})
diff --git a/plans/phases.md b/plans/phases.md
index b2dbe8c..a91d17d 100644
--- a/plans/phases.md
+++ b/plans/phases.md
@@ -789,7 +789,7 @@ export function useSpeechInputWithCursor(
**Goal:** Create comprehensive documentation and example applications.
-### 5.1 README.md Structure
+### 4.1 README.md Structure
```markdown
# @syntropy-labs/react-web-speech
@@ -816,14 +816,14 @@ export function useSpeechInputWithCursor(
## License
```
-### 5.2 Example Application
+### 4.2 Example Application
Create a `/examples` directory with:
- `examples/basic/` — Minimal React + Vite app
- `examples/nextjs/` — Next.js App Router integration
- `examples/form/` — Complex form with speech input
-### 5.3 API Documentation
+### 4.3 API Documentation
- JSDoc comments on all exports
- TypeDoc-generated API reference