Alpine.js Integration
XKin integrates elegantly with Alpine.js to provide powerful styling capabilities with minimal code. This guide demonstrates how to combine XKin's component system with Alpine's reactive attributes.
Overview
Alpine.js is a minimal JavaScript framework for adding interactivity to your HTML with simple directives. XKin enhances Alpine applications by providing:
- Component-based styling with themes
- Centralized styling logic
- Reactive style updates based on state
Basic Integration
The integration involves: 1. Creating XKin component definitions 2. Registering custom Alpine directives and magic methods 3. Using them in your HTML with Alpine's reactivity system
Complete Example
HTML Structure (index.html
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>XKin + Alpine Integration</title>
<!-- Alpine.js from CDN -->
<script src="//unpkg.com/alpinejs" defer></script>
<!-- XKin from CDN -->
<script src="https://unpkg.com/xkin@latest"></script>
<!-- Our custom integration -->
<script src="./theme-integration.js" defer></script>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
padding: 2rem;
max-width: 800px;
margin: 0 auto;
}
.button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: all 0.2s ease;
}
.is-small {
font-size: 0.875rem;
padding: 6px 12px;
}
.is-large {
font-size: 1.125rem;
padding: 10px 20px;
}
.is-disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style>
</head>
<body>
<h1>XKin + Alpine.js Demo</h1>
<div x-data="{
count: 0,
mode: null,
disabled: false,
toggleMode() {
this.mode = this.mode ? null : 'active';
}
}">
<div class="control-panel">
<h2>Counter: <span x-text="count"></span></h2>
<div>
<label>
<input type="checkbox" x-model="disabled"> Disable button
</label>
<button @click="toggleMode()" x-text="mode ? 'Remove Theme' : 'Apply Theme'"></button>
</div>
</div>
<div class="button-showcase">
<button
@click="count++"
x-theme="$theme('button', mode, {
size: 'sm',
height: '40px',
disabled: disabled
})"
>
Increment Count
</button>
</div>
</div>
</body>
</html>
JavaScript Implementation (theme-integration.js
)
// Define our component library using XKin
const ComponentLibrary = {
// Button component definition
button: xkin.component({
base: {
class: "button",
style: "outline: none; margin: 10px 0;",
},
setup: ({ size, disabled, height }) => ({
class: {
"is-small": size === "sm",
"is-large": size === "lg",
"is-disabled": disabled,
},
style: {
height: height || "auto",
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
},
}),
theme: {
active: {
class: "color-bg-success color-tx-white",
style: "transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0,0,0,0.15);",
},
error: {
class: "color-bg-danger color-tx-white",
style: "",
},
warning: {
class: "color-bg-warning",
style: "",
},
},
}),
// Add more components as needed
card: xkin.component({
// Card component definition...
}),
};
// Configure XKin theme (optional)
xkin.theme.set({
base: {
success: "#38c172",
danger: "#e3342f",
warning: "#f6993f",
},
});
// Wait for Alpine to initialize
document.addEventListener("alpine:init", () => {
// Register a magic helper to create theme configurations
Alpine.magic("theme", () => {
return (componentName, themeName, props = {}) => ({
name: componentName,
theme: themeName,
props: props,
});
});
// Register the theme directive
Alpine.directive("theme", (el, { expression }, { evaluateLater, effect }) => {
// Create evaluator for the expression
const getThemeData = evaluateLater(expression);
// Set up reactive effect
effect(() => {
getThemeData((data) => {
// Get the requested component
const component = ComponentLibrary[data.name];
if (!component) {
console.warn(`XKin component "${data.name}" not found`);
return;
}
// Generate styles with the current props and theme
const styles = component(data.props).theme(data.theme);
// Apply to the element
el.className = styles.class;
el.style = styles.style;
});
});
});
});
Advanced Usage
Multiple Theme Components
<div x-data="{ activeTab: 'home' }">
<!-- Navigation tabs with different themes -->
<div class="tabs">
<button
@click="activeTab = 'home'"
x-theme="$theme('tab', activeTab === 'home' ? 'active' : null, { size: 'sm' })"
>
Home
</button>
<button
@click="activeTab = 'profile'"
x-theme="$theme('tab', activeTab === 'profile' ? 'active' : null, { size: 'sm' })"
>
Profile
</button>
<button
@click="activeTab = 'settings'"
x-theme="$theme('tab', activeTab === 'settings' ? 'active' : null, { size: 'sm' })"
>
Settings
</button>
</div>
<!-- Content panels -->
<div x-show="activeTab === 'home'">Home content</div>
<div x-show="activeTab === 'profile'">Profile content</div>
<div x-show="activeTab === 'settings'">Settings content</div>
</div>
Dynamically Switching Themes
<div x-data="{ darkMode: false }">
<button
@click="darkMode = !darkMode"
x-theme="$theme('iconButton', null, {})"
>
<span x-show="darkMode">🌙</span>
<span x-show="!darkMode">☀️</span>
</button>
<div x-theme="$theme('card', darkMode ? 'dark' : 'light', {
elevated: true,
padding: 'large'
})">
<h2>Content Card</h2>
<p>This card changes theme when dark mode is toggled.</p>
</div>
</div>
Performance Considerations
For optimal performance when using XKin with Alpine:
- Define your components outside the Alpine initialization to avoid recreating them
- Use
xkin.memoizeOne
for expensive style calculations - For lists or repeated components, define the component once and reuse it
// Optimize component for better performance
const optimizedButton = xkin.component({
// ...component definition
setup: xkin.memoizeOne(props => ({
// Complex styling logic here
}))
});
// Add to your component library
ComponentLibrary.optimizedButton = optimizedButton;
Integration with Alpine Plugins
XKin works seamlessly with Alpine plugins like Alpine Focus, Intersect, and Collapse: