README Examples
A gallery of small examples that mirror the package README snippets.
Getting Started - Counter
Component State - Counter
Count: 0
Greeting
Hello, World!
Counter with Setup
Total: 10
Setup vs Props
Score: 5
Events - Search Input
Controlled Input - Slug Form
Global Events - Keyboard Tracker
Keys: (press some keys)
CSS Prop - Basic Button
CSS Prop - Advanced Button
Ref Mixin - Form
Ref with AbortSignal - Resize Observer
Resize me! (0 × 0)
handle.update() - Player
handle.queueTask - Scroll to Section
handle.signal - Clock
5:58:43 PM
handle.id - Labeled Input
Context API - Theme Provider
Context with EventTarget
Current theme: light
Fragments - List
- Item 1
- Item 2
- Item 3
import {
addEventListeners,
css,
on,
ref,
TypedEventTarget,
type Handle,
type RemixNode,
} from 'remix/ui'
// ============================================================================
// Getting Started - Basic App Example
// ============================================================================
function App(handle: Handle) {
let count = 0
return () => (
<button
mix={[
on('click', () => {
count++
handle.update()
}),
]}
>
Count: {count}
</button>
)
}
// ============================================================================
// Component State and Updates - Counter
// ============================================================================
function Counter(handle: Handle) {
let count = 0
return () => (
<div>
<span>Count: {count}</span>
<button
mix={[
on('click', () => {
count++
handle.update()
}),
]}
>
Increment
</button>
</div>
)
}
// ============================================================================
// Components - Greeting
// ============================================================================
function Greeting(handle: Handle<{ name: string }>) {
return () => <h1>Hello, {handle.props.name}!</h1>
}
// ============================================================================
// 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 () => (
<div>
{handle.props.label || 'Count'}: {count}
<button
mix={[
on('click', () => {
count++
handle.update()
}),
]}
>
Increment
</button>
</div>
)
}
// ============================================================================
// Setup Prop vs Props - CounterWithLabel
// ============================================================================
function CounterWithLabel(
handle: Handle<{ initialCount: number; label?: string }>,
) {
let count = handle.props.initialCount
return () => (
<div>
{handle.props.label}: {count}
<button
mix={[
on('click', () => {
count++
handle.update()
}),
]}
>
+
</button>
</div>
)
}
// ============================================================================
// Events - SearchInput
// ============================================================================
function SearchInput(handle: Handle) {
let query = ''
let results: string[] = []
let loading = false
return () => (
<div>
<input
type="text"
value={query}
placeholder="Type to search..."
mix={[
on('input', (event, signal) => {
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 && <div>Loading...</div>}
{!loading && results.length > 0 && (
<ul>
{results.map((r) => (
<li>{r}</li>
))}
</ul>
)}
</div>
)
}
// ============================================================================
// Controlled Input - Slug Form
// ============================================================================
function SlugForm(handle: Handle) {
let slug = ''
let generatedSlug = ''
return () => (
<form>
<label mix={[css({ display: 'flex', alignItems: 'center', gap: '8px' })]}>
<input
type="checkbox"
mix={[
on('change', (event) => {
if (event.currentTarget.checked) {
generatedSlug = crypto.randomUUID().slice(0, 8)
} else {
generatedSlug = ''
}
handle.update()
}),
]}
/>
Auto-generate slug
</label>
<label
mix={[css({ display: 'flex', flexDirection: 'column', gap: '4px' })]}
>
Slug
<input
type="text"
value={generatedSlug || slug}
disabled={!!generatedSlug}
mix={[
on('input', (event) => {
slug = event.currentTarget.value
handle.update()
}),
]}
/>
</label>
</form>
)
}
// ============================================================================
// Global Events - KeyboardTracker
// ============================================================================
function KeyboardTracker(handle: Handle) {
let keys: string[] = []
handle.queueTask(() => {
addEventListeners(document, handle.signal, {
keydown: (event) => {
keys.push(event.key)
if (keys.length > 10) keys.shift()
handle.update()
},
})
})
return () => <div>Keys: {keys.join(', ') || '(press some keys)'}</div>
}
// ============================================================================
// CSS Prop - Button (Basic)
// ============================================================================
function ButtonBasic() {
return () => (
<button
mix={[
css({
color: 'white',
backgroundColor: 'rgb(54, 113, 246)',
border: 'none',
padding: '8px 16px',
borderRadius: '4px',
cursor: 'pointer',
'&:hover': {
backgroundColor: 'rgb(37, 90, 210)',
},
'&:active': {
transform: 'scale(0.98)',
},
}),
]}
>
Click me
</button>
)
}
// ============================================================================
// CSS Prop - Button (Advanced with nested rules)
// ============================================================================
function ButtonAdvanced() {
return () => (
<button
mix={[
css({
color: 'white',
backgroundColor: 'rgb(54, 113, 246)',
border: 'none',
padding: '10px 20px',
borderRadius: '6px',
cursor: 'pointer',
position: 'relative',
display: 'inline-flex',
alignItems: 'center',
gap: '8px',
'&:hover': {
backgroundColor: 'rgb(37, 90, 210)',
},
'&::before': {
content: '""',
position: 'absolute',
inset: '-2px',
borderRadius: '8px',
background:
'linear-gradient(45deg, rgb(54, 113, 246), rgb(99, 179, 255))',
zIndex: -1,
opacity: 0,
transition: 'opacity 0.2s',
},
'&:hover::before': {
opacity: 1,
},
'.icon': {
width: '16px',
height: '16px',
},
}),
]}
>
<span className="icon">★</span>
Click me
</button>
)
}
// ============================================================================
// Ref Mixin - Form (Basic)
// ============================================================================
function FormBasic() {
let inputRef: HTMLInputElement
return () => (
<div>
<input
type="text"
placeholder="Click the button to select this"
// capture the input node
mix={[
ref((node) => (inputRef = node)),
css({ marginRight: '8px', padding: '4px 8px' }),
]}
/>
<button
mix={[
css({ padding: '4px 12px' }),
on('click', () => {
// Select it from other parts of the form
inputRef.select()
}),
]}
>
Select Input
</button>
</div>
)
}
// ============================================================================
// Ref Mixin with AbortSignal - ResizeObserver Component
// ============================================================================
function ResizeComponent(handle: Handle) {
let dimensions = { width: 0, height: 0 }
return () => (
<div
mix={[
ref((node, signal) => {
// 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})
</div>
)
}
// ============================================================================
// handle.update() - Player
// ============================================================================
function Player(handle: Handle) {
let isPlaying = false
let playButton: HTMLButtonElement
let stopButton: HTMLButtonElement
return () => (
<div mix={[css({ display: 'flex', gap: '8px' })]}>
<button
disabled={isPlaying}
mix={[
ref((node) => (playButton = node)),
css({
padding: '8px 16px',
opacity: isPlaying ? 0.5 : 1,
}),
on('click', async () => {
isPlaying = true
await handle.update()
// Focus the enabled button after update completes
stopButton.focus()
}),
]}
>
▶ Play
</button>
<button
disabled={!isPlaying}
mix={[
ref((node) => (stopButton = node)),
css({
padding: '8px 16px',
opacity: !isPlaying ? 0.5 : 1,
}),
on('click', async () => {
isPlaying = false
await handle.update()
// Focus the enabled button after update completes
playButton.focus()
}),
]}
>
⏹ Stop
</button>
</div>
)
}
// ============================================================================
// handle.queueTask - Form with scroll
// ============================================================================
function FormWithScroll(handle: Handle) {
let showDetails = false
let detailsSection: HTMLElement
return () => (
<div>
<label
mix={[
css({
display: 'flex',
alignItems: 'center',
gap: '8px',
cursor: 'pointer',
}),
]}
>
<input
type="checkbox"
checked={showDetails}
mix={[
on('change', (event) => {
showDetails = event.currentTarget.checked
handle.update()
if (showDetails) {
// Scroll to the expanded section after it renders
handle.queueTask(() => {
detailsSection.scrollIntoView({
behavior: 'smooth',
block: 'start',
})
})
}
}),
]}
/>
Show additional details
</label>
{showDetails && (
<section
mix={[
ref((node) => (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)',
}),
]}
>
<h3 mix={[css({ margin: '0 0 0.5rem 0' })]}>Additional Details</h3>
<p mix={[css({ margin: 0 })]}>
This section appears when the checkbox is checked.
</p>
</section>
)}
</div>
)
}
// ============================================================================
// handle.signal - Clock
// ============================================================================
function Clock(handle: Handle) {
handle.queueTask(() => {
let interval = setInterval(() => {
handle.update()
}, 1000)
handle.signal.addEventListener('abort', () => clearInterval(interval), {
once: true,
})
})
return () => <span>{new Date().toLocaleTimeString()}</span>
}
// ============================================================================
// handle.id - LabeledInput
// ============================================================================
function LabeledInput(handle: Handle) {
return () => (
<div mix={[css({ display: 'flex', flexDirection: 'column', gap: '4px' })]}>
<label htmlFor={handle.id}>Name</label>
<input
id={handle.id}
type="text"
mix={[
css({
padding: '4px 8px',
borderRadius: '4px',
border: '1px solid rgba(255,255,255,0.3)',
}),
]}
/>
</div>
)
}
// ============================================================================
// Context API - Theme Provider and Consumer
// ============================================================================
function ThemeProvider(handle: Handle<{}, { theme: string }>) {
handle.context.set({ theme: 'dark' })
return () => (
<div mix={[css({ display: 'flex', flexDirection: 'column', gap: '8px' })]}>
<ThemedHeader />
</div>
)
}
function ThemedHeader(handle: Handle) {
// Consume context from ThemeProvider
let { theme } = handle.context.get(ThemeProvider)
return () => (
<header
mix={[
css({
backgroundColor: theme === 'dark' ? '#000' : '#fff',
color: theme === 'dark' ? '#fff' : '#000',
}),
]}
>
Header
</header>
)
}
// ============================================================================
// 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 () => (
<div mix={[css({ display: 'flex', flexDirection: 'column', gap: '8px' })]}>
<button
mix={[
css({ padding: '8px 16px', alignSelf: 'flex-start' }),
on('click', () => {
// no updates in the parent component
theme.setValue(theme.value === 'light' ? 'dark' : 'light')
}),
]}
>
Toggle Theme (EventTarget)
</button>
<ThemedContent />
</div>
)
}
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 () => (
<div
mix={[
css({
padding: '12px',
borderRadius: '6px',
backgroundColor: theme.value === 'dark' ? '#1a1a1a' : '#f0f0f0',
color: theme.value === 'dark' ? '#fff' : '#000',
}),
]}
>
Current theme: {theme.value}
</div>
)
}
// ============================================================================
// Fragments - List
// ============================================================================
function ListWithFragment() {
return () => (
<ul mix={[css({ margin: 0, paddingLeft: '20px' })]}>
<>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</>
</ul>
)
}
// ============================================================================
// Example Container Component
// ============================================================================
function Example(handle: Handle<{ title: string; children: RemixNode }>) {
return () => (
<div className="example">
<h2>{handle.props.title}</h2>
<div className="example-content">{handle.props.children}</div>
</div>
)
}
// ============================================================================
// Main Demo App
// ============================================================================
export default function DemoApp() {
return () => (
<div className="examples-grid">
<Example title="Getting Started - Counter">
<App />
</Example>
<Example title="Component State - Counter">
<Counter />
</Example>
<Example title="Greeting">
<Greeting name="World" />
</Example>
<Example title="Counter with Setup">
<CounterWithSetup initialCount={10} label="Total" />
</Example>
<Example title="Setup vs Props">
<CounterWithLabel initialCount={5} label="Score" />
</Example>
<Example title="Events - Search Input">
<SearchInput />
</Example>
<Example title="Controlled Input - Slug Form">
<SlugForm />
</Example>
<Example title="Global Events - Keyboard Tracker">
<KeyboardTracker />
</Example>
<Example title="CSS Prop - Basic Button">
<ButtonBasic />
</Example>
<Example title="CSS Prop - Advanced Button">
<ButtonAdvanced />
</Example>
<Example title="Ref Mixin - Form">
<FormBasic />
</Example>
<Example title="Ref with AbortSignal - Resize Observer">
<ResizeComponent />
</Example>
<Example title="handle.update() - Player">
<Player />
</Example>
<Example title="handle.queueTask - Scroll to Section">
<FormWithScroll />
</Example>
<Example title="handle.signal - Clock">
<Clock />
</Example>
<Example title="handle.id - Labeled Input">
<LabeledInput />
</Example>
<Example title="Context API - Theme Provider">
<ThemeProvider />
</Example>
<Example title="Context with EventTarget">
<ThemeProviderAdvanced />
</Example>
<Example title="Fragments - List">
<ListWithFragment />
</Example>
</div>
)
}