Svelte integration library for the Arizona Framework - seamlessly embed Svelte components in Arizona applications with automatic lifecycle management.
Add to your rebar.config:
{deps, [
arizona,
arizona_svelte
]}.npm install @arizona-framework/svelte svelteThe easiest way to get started is using the rebar3_arizona plugin to
create a new app with Svelte template:
rebar3 arizona
# Choose "Create new app"
# Enter an app name
# Select the "Svelte template"rebar3 new arizona.svelte my_svelte_appcd my_svelte_app
npm install # Install dependencies
npm run build # Build CSS and JS
rebar3 shell # Start the Arizona serverYour app will be available at http://localhost:1912 with hot-reloading for both CSS and Svelte components.
The Svelte template includes:
- Svelte 5 with modern
$stateand$effectrunes - Vite build system and Tailwind CSS
- Example HelloWorld and Counter components
- Full integration with Arizona's real-time WebSocket updates
If you want to add Svelte to an existing Arizona project, follow the installation and quick start sections below.
% In your Arizona view module
-module(my_view).
-export([render/1, my_page/1]).
my_page(_Bindings) ->
arizona_template:from_html(~"""
<div>
<h1>My Arizona + Svelte App</h1>
{% Render a Svelte component }
{arizona_svelte:render_component("Counter", #{
title => "My Counter",
initialCount => 5
})}
{% Render another component with Arizona bindings }
{arizona_svelte:render_component("HelloWorld", #{
name => arizona_template:get_binding(user_name, Bindings)
})}
</div>
""").// assets/js/main.js
import ArizonaSvelte from '@arizona-framework/svelte';
import Counter from '../svelte/components/Counter.svelte';
import HelloWorld from '../svelte/components/HelloWorld.svelte';
// Option 1: Register components in constructor (recommended)
const arizonaSvelte = new ArizonaSvelte({
components: {
Counter,
HelloWorld
}
});
// Option 2: Register components manually
// const arizonaSvelte = new ArizonaSvelte();
// arizonaSvelte.registerComponents({
// Counter,
// HelloWorld
// });
// Start automatic component lifecycle monitoring
arizonaSvelte.startMonitoring({
autoMount: true, // Automatically mount components found in DOM
autoUnmount: true, // Automatically unmount removed components
observeSubtree: true, // Monitor entire DOM tree
debounceMs: 0 // No delay for immediate responsiveness
});
// Make available globally for debugging
globalThis.arizonaSvelte = arizonaSvelte;Create a centralized component index:
// svelte/components/index.js
export { default as Counter } from './Counter.svelte';
export { default as HelloWorld } from './HelloWorld.svelte';
export { default as Dashboard } from './Dashboard.svelte';Then import all at once:
// assets/js/main.js
import ArizonaSvelte from '@arizona-framework/svelte';
import * as components from '../svelte/components/index.js';
const arizonaSvelte = new ArizonaSvelte({ components });
arizonaSvelte.startMonitoring();<!-- assets/svelte/components/Counter.svelte -->
<script>
let { title, initialCount = 0 } = $props();
let count = $state(initialCount);
</script>
<div class="counter">
<h2>{title}</h2>
<p>Count: {count}</p>
<button onclick={() => count++}>+</button>
<button onclick={() => count--}>-</button>
</div>Components are rendered inside a wrapper <div> element that serves as the mount
target for Svelte components. This wrapper is necessary for the JavaScript side
to discover and manage component lifecycle.
When you render a component:
arizona_svelte:render_component("Counter", #{initialCount => 5})The resulting DOM structure is:
<!-- Wrapper element (created by Arizona) -->
<div
data-svelte-component="Counter"
data-svelte-props='{"initialCount":5}'
data-arizona-update="false"
>
<!-- Component content (rendered by Svelte) -->
<div class="counter">
<h2>Count: 5</h2>
<button>Increment</button>
</div>
</div>Why the wrapper exists:
data-svelte-component- JavaScript identifies which component to mountdata-svelte-props- Props are passed to the componentdata-arizona-update="false"- Prevents Arizona from interfering with Svelte's DOM management- Provides a stable mount target for component lifecycle management
You can add custom HTML attributes to the wrapper for styling and layout control.
% Simple string attributes
arizona_svelte:render_component("Card", #{}, ~"class=\"flex-1 p-4\" id=\"main-card\"")% Conditional attributes based on bindings
arizona_svelte:render_component("Widget", #{}, arizona_template:from_string(~"""
class="widget"
{case arizona_template:get_binding(hidden, Bindings) of
true -> ~"hidden";
false -> ~""
end}
"""))The wrapper div can interfere with flexbox/grid layouts:
% Without custom attributes - wrapper breaks layout
<div class="flex gap-4">
{arizona_svelte:render_component("Card", #{})}
{arizona_svelte:render_component("Card", #{})}
</div>Problem: The wrapper <div> becomes the flex child, not the Card component itself.
<!-- Resulting DOM - wrapper is the flex child -->
<div class="flex gap-4">
<div data-svelte-component="Card"> <!-- This is the flex child -->
<div class="card">...</div> <!-- Card content is nested -->
</div>
<div data-svelte-component="Card">
<div class="card">...</div>
</div>
</div>Solution: Use display: contents to make the wrapper transparent to layout:
% Wrapper becomes invisible - parent sees Card's children directly
<div class="flex gap-4">
{arizona_svelte:render_component("Card", #{}, ~"style=\"display: contents\"")}
{arizona_svelte:render_component("Card", #{}, ~"style=\"display: contents\"")}
</div><!-- Resulting behavior - Card's root is the flex child -->
<div class="flex gap-4">
<div data-svelte-component="Card" style="display: contents">
<div class="card">...</div> <!-- This behaves as the flex child -->
</div>
<div data-svelte-component="Card" style="display: contents">
<div class="card">...</div>
</div>
</div>Other layout options:
% Add flex/grid classes to the wrapper itself
arizona_svelte:render_component("Card", #{}, ~"class=\"flex-1\"")
arizona_svelte:render_component("GridItem", #{}, ~"class=\"col-span-2\"")Note: display: contents has
accessibility limitations with
buttons and certain ARIA roles. Test with screen readers if accessibility is
critical.
- 🔄 Automatic Lifecycle Management: Components mount/unmount automatically when DOM changes
- 🏗️ Flexible Component Registration: Register components via constructor or batch methods
- 📡 Real-time Integration: Works seamlessly with Arizona's WebSocket updates
- 🎯 Simple Setup: Register components and start monitoring in a few lines
- 🧪 Development Friendly: Built-in logging and debugging support
- ⚡ High Performance: Zero-debounce monitoring for immediate responsiveness
- 🎨 Customizable Wrappers: Add classes, IDs, styles, and dynamic attributes
Critical Behavior: Svelte components rendered via arizona_svelte:render_component/2
maintain independent state from Arizona. Arizona bindings in component props are
evaluated only once during initial render and are NOT tracked for changes.
%% This binding is evaluated once and never updated
{arizona_svelte:render_component("UserDashboard", #{
userId => arizona_template:get_binding(current_user, Bindings)
})}If current_user changes in Arizona state, the Svelte component will NOT automatically
receive the new value. The component maintains its own reactive state using Svelte's
$state runes.
Renders a Svelte component with props.
arizona_svelte:render_component(Component, Props) -> Template.Component :: binary()- Name of the Svelte componentProps :: #{atom() | binary() => json:encode_value()}- Component props- Returns:
arizona_template:render_callback()
Creates a new Arizona Svelte instance.
Options:
components: Object- Components to register on instantiation
const arizonaSvelte = new ArizonaSvelte({
components: {
Counter: CounterComponent,
HelloWorld: HelloWorldComponent
}
});Register multiple components at once.
arizonaSvelte.registerComponents({
Dashboard: DashboardComponent,
Modal: ModalComponent
});Starts automatic component lifecycle monitoring.
Options:
autoMount: boolean- Auto-mount new components (default: true)autoUnmount: boolean- Auto-unmount removed components (default: true)observeSubtree: boolean- Monitor entire DOM tree (default: true)debounceMs: number- Debounce delay in milliseconds (default: 0)
arizonaSvelte.stopMonitoring()- Stop monitoringarizonaSvelte.isMonitoring()- Check if monitoring is activearizonaSvelte.getRegistry()- Get component registryarizonaSvelte.getLifecycle()- Get lifecycle managerarizonaSvelte.getComponent(name)- Get a specific componentarizonaSvelte.hasComponent(name)- Check if component is registeredarizonaSvelte.getComponentNames()- Get all registered component names
- Erlang/OTP: 28+
- Arizona Framework
- Svelte
- Node.js: 18.0.0+
If you like this tool, please consider sponsoring me. I'm thankful for your never-ending support ❤️
I also accept coffees ☕
Copyright (c) 2025 William Fank Thomé
Arizona Svelte is 100% open-source and community-driven. All components are available under the Apache 2 License on GitHub.
See LICENSE.md for more information.
