import {
addEventListeners,
css,
on,
ref,
TypedEventTarget,
type Handle,
type RemixNode,
} from '@remix-run/ui'
// ============================================================================
// Getting Started - Basic App Example
// ============================================================================
function App(handle: Handle) {
let count = 0
return () => (
)
}
// ============================================================================
// Component State and Updates - Counter
// ============================================================================
function Counter(handle: Handle) {
let count = 0
return () => (
Count: {count}
)
}
// ============================================================================
// Components - Greeting
// ============================================================================
function Greeting(handle: Handle<{ name: string }>) {
return () => Hello, {handle.props.name}!
}
// ============================================================================
// Stateful Components - CounterWithSetup
// ============================================================================
function CounterWithSetup(handle: Handle<{ initialCount: number; label?: string }>) {
// Component function: runs once
let count = handle.props.initialCount
// Return render function: runs on every update
return () => (
{handle.props.label || 'Count'}: {count}
)
}
// ============================================================================
// Setup Prop vs Props - CounterWithLabel
// ============================================================================
function CounterWithLabel(handle: Handle<{ initialCount: number; label?: string }>) {
let count = handle.props.initialCount
return () => (
{handle.props.label}: {count}
)
}
// ============================================================================
// Events - SearchInput
// ============================================================================
function SearchInput(handle: Handle) {
let query = ''
let results: string[] = []
let loading = false
return () => (
{
query = event.currentTarget.value
loading = true
handle.update()
// Simulated search with timeout
setTimeout(() => {
if (signal.aborted) return
results = query ? [`Result for "${query}" 1`, `Result for "${query}" 2`] : []
loading = false
handle.update()
}, 300)
}),
]}
/>
{loading &&
Loading...
}
{!loading && results.length > 0 && (
{results.map((r) => (
- {r}
))}
)}
)
}
// ============================================================================
// Controlled Input - Slug Form
// ============================================================================
function SlugForm(handle: Handle) {
let slug = ''
let generatedSlug = ''
return () => (
)
}
// ============================================================================
// Global Events - KeyboardTracker
// ============================================================================
function KeyboardTracker(handle: Handle) {
let keys: string[] = []
addEventListeners(document, handle.signal, {
keydown: (event) => {
keys.push(event.key)
if (keys.length > 10) keys.shift()
handle.update()
},
})
return () => Keys: {keys.join(', ') || '(press some keys)'}
}
// ============================================================================
// CSS Prop - Button (Basic)
// ============================================================================
function ButtonBasic() {
return () => (
)
}
// ============================================================================
// CSS Prop - Button (Advanced with nested rules)
// ============================================================================
function ButtonAdvanced() {
return () => (
)
}
// ============================================================================
// Ref Mixin - Form (Basic)
// ============================================================================
function FormBasic() {
let inputRef: HTMLInputElement
return () => (
(inputRef = node)), css({ marginRight: '8px', padding: '4px 8px' })]}
/>
)
}
// ============================================================================
// Ref Mixin with AbortSignal - ResizeObserver Component
// ============================================================================
function ResizeComponent(handle: Handle) {
let dimensions = { width: 0, height: 0 }
return () => (
{
// Set up something that needs cleanup
let observer = new ResizeObserver((entries) => {
let entry = entries[0]
if (entry) {
dimensions.width = Math.round(entry.contentRect.width)
dimensions.height = Math.round(entry.contentRect.height)
handle.update()
}
})
observer.observe(node)
// Clean up when element is removed
signal.addEventListener('abort', () => {
observer.disconnect()
})
}),
css({
padding: '20px',
backgroundColor: 'rgba(255, 255, 255, 0.1)',
borderRadius: '8px',
resize: 'both',
overflow: 'auto',
minWidth: '100px',
minHeight: '60px',
border: '1px solid rgb(209, 213, 219)',
}),
]}
>
Resize me! ({dimensions.width} × {dimensions.height})
)
}
// ============================================================================
// handle.update() - Player
// ============================================================================
function Player(handle: Handle) {
let isPlaying = false
let playButton: HTMLButtonElement
let stopButton: HTMLButtonElement
return () => (
)
}
// ============================================================================
// handle.queueTask - Form with scroll
// ============================================================================
function FormWithScroll(handle: Handle) {
let showDetails = false
let detailsSection: HTMLElement
return () => (
{showDetails && (
(detailsSection = node)),
css({
marginTop: '1rem',
padding: '1rem',
border: '1px solid rgba(255, 255, 255, 0.2)',
borderRadius: '8px',
backgroundColor: 'rgba(255, 255, 255, 0.05)',
}),
]}
>
Additional Details
This section appears when the checkbox is checked.
)}
)
}
// ============================================================================
// handle.signal - Clock
// ============================================================================
function Clock(handle: Handle) {
let interval = setInterval(() => {
// clear the interval when the component is disconnected
if (handle.signal.aborted) {
clearInterval(interval)
return
}
handle.update()
}, 1000)
return () => {new Date().toLocaleTimeString()}
}
// ============================================================================
// handle.id - LabeledInput
// ============================================================================
function LabeledInput(handle: Handle) {
return () => (
)
}
// ============================================================================
// Context API - Theme Provider and Consumer
// ============================================================================
function ThemeProvider(handle: Handle<{}, { theme: string }>) {
handle.context.set({ theme: 'dark' })
return () => (
)
}
function ThemedHeader(handle: Handle) {
// Consume context from ThemeProvider
let { theme } = handle.context.get(ThemeProvider)
return () => (
)
}
// ============================================================================
// Context API with EventTarget - Advanced Theme
// ============================================================================
class Theme extends TypedEventTarget<{ change: Event }> {
#value: 'light' | 'dark' = 'light'
get value() {
return this.#value
}
setValue(value: 'light' | 'dark') {
this.#value = value
this.dispatchEvent(new Event('change'))
}
}
function ThemeProviderAdvanced(handle: Handle<{}, Theme>) {
let theme = new Theme()
handle.context.set(theme)
return () => (
)
}
function ThemedContent(handle: Handle) {
let theme = handle.context.get(ThemeProviderAdvanced)
// Subscribe to theme changes and update when it changes
addEventListeners(theme, handle.signal, {
change() {
handle.update()
},
})
return () => (
Current theme: {theme.value}
)
}
// ============================================================================
// Fragments - List
// ============================================================================
function ListWithFragment() {
return () => (
<>
- Item 1
- Item 2
- Item 3
>
)
}
// ============================================================================
// Example Container Component
// ============================================================================
function Example(handle: Handle<{ title: string; children: RemixNode }>) {
return () => (
{handle.props.title}
{handle.props.children}
)
}
// ============================================================================
// Main Demo App
// ============================================================================
/**
* @name README Examples
* @description A gallery of small examples that mirror the package README snippets.
*/
export default function DemoApp() {
return () => (
)
}