04.10.2025
Rails 7 with Svelte: Step-by-Step Setup

Dariusz Michalski
CEO
Learn how to seamlessly integrate Rails 7 with Svelte for a fast, modern web application that caters to Swiss localization needs.
<h1>Svelte {title}</h1>
// Format currency in CHF
const formatCurrency = (amount) => {
return new Intl.NumberFormat('de-CH', {
style: 'currency',
currency: 'CHF'
}).format(amount);
};
// Format date to Swiss style
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('de-CH', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
};
// Format temperature in Celsius
const formatTemperature = (temp) => {
return ${temp}°C
;
};
</script>
<div class="user-profile">
<h2>{name}</h2>
<p><strong>Member since:</strong> {formatDate(joinDate)}</p>
<p><strong>Account balance:</strong> {formatCurrency(accountBalance)}</p>
<p><strong>Local temperature:</strong> {formatTemperature(temperature)}</p>
</div>
<style>
.user-profile {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
background-color: #f9f9f9;
}
.user-profile h2 {
margin-top: 0;
color: #333;
}
.user-profile p {
margin: 0.5rem 0;
}
</style>
private
def fetch_local_temperature
# Fetch from a weather API
22.5
end
end
<%= svelte_component('UserProfile', @user_data) %>
<p>Additional Rails content can go here...</p>
</div>
const increment = () => {
count += 1;
};
const decrement = () => {
count -= 1;
};
</script>
<div class="counter">
<button on:click={decrement}>-</button>
<span class="count">{count.toLocaleString('de-CH')}</span>
<button on:click={increment}>+</button>
</div>
const components = {
ProductCard
};
export function registerSvelteComponents() {
Object.keys(components).forEach(key => {
const elements = document.querySelectorAll([data-svelte-component='${key}']
);
});
}
end
end
export let productId;
let product = null;
let loading = true;
let error = null;
onMount(async () => {
try {
const response = await fetch(/api/products/${productId}
, {
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
});
});
</script>
{#if loading}
<p>Loading product information...</p>
{:else if error}
<p class="error">Error: {error}</p>
{:else if product}
<div class="product-info">
<h3>{product.name}</h3>
<p class="price">{product.formatted_price}</p>
<small>Last updated: {product.updated_at}</small>
</div>
{/if}
<%= svelte_component('ProductFilter', props: {
categories: @categories.map(&:name),
price_range: { min: 0, max: 5000 }
}) %>
<div id="products-container">
<%= render partial: 'product', collection: @products %>
</div>
</div>
export function formatNumber(number, locale = 'de-CH') {
return new Intl.NumberFormat(locale, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(number);
}
export function formatDate(date, locale = 'de-CH') {
return new Intl.DateTimeFormat(locale, {
day: '2-digit',
month: '2-digit',
year: 'numeric'
}).format(new Date(date));
}
export function formatDateTime(date, locale = 'de-CH') {
return new Intl.DateTimeFormat(locale, {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
}).format(new Date(date));
}
export let product;
export let locale = 'de-CH'; // Default to German-Swiss
$: formattedPrice = formatCurrency(product.price, locale);
$: formattedDiscount = product.discount ? formatNumber(product.discount, locale) + '%' : null;
$: formattedDate = formatDate(product.created_at, locale);
</script>
<div class="product-card">
<h3>{product.name}</h3>
<div class="price-info">
<span class="current-price">{formattedPrice}</span>
{#if formattedDiscount}
<span class="discount">-{formattedDiscount}</span>
{/if}
</div>
<p class="product-details">
Weight: {formatNumber(product.weight_kg, locale)} kg<br>
Added: {formattedDate}
</p>
</div>
export const currentLocale = writable('de-CH');
return ${formatted} °C
;
}
export function formatDistance(metres, locale = 'de-CH') {
if (metres >= 1000) {
const km = metres / 1000;
return new Intl.NumberFormat(locale, {
minimumFractionDigits: 1,
maximumFractionDigits: 2
}).format(km) + ' km';
}
return new Intl.NumberFormat(locale, {
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(metres) + ' m';
}
export function formatWeight(grams, locale = 'de-CH') {
if (grams >= 1000) {
const kg = grams / 1000;
return new Intl.NumberFormat(locale, {
minimumFractionDigits: 0,
maximumFractionDigits: 2
}).format(kg) + ' kg';
}
return new Intl.NumberFormat(locale, {
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(grams) + ' g';
}
export let weatherData;
$: temperature = formatTemperature(weatherData.temperature, $currentLocale);
$: visibility = formatDistance(weatherData.visibility_metres, $currentLocale);
</script>
<div class="weather-widget">
<div class="temperature">
<span class="value">{temperature}</span>
<span class="condition">{weatherData.condition}</span>
</div>
<div class="details">
<p>Visibility: {visibility}</p>
<p>Humidity: {weatherData.humidity}%</p>
</div>
</div>