A basic blog with posts and comments demonstrating component composition.
import Eleva from "eleva";
const app = new Eleva("BlogApp");
// Comment Component
app.component("Comment", {
setup({ props }) {
return { comment: props.comment };
},
template: (ctx) => `
<div class="comment">
<div class="comment-header">
<strong>${ctx.comment.author}</strong>
<span class="date">${ctx.comment.date}</span>
</div>
<p>${ctx.comment.text}</p>
</div>
`
});
// Post Component
app.component("BlogPost", {
setup({ signal, props }) {
const post = props.post;
const showComments = signal(false);
const newComment = signal("");
const comments = signal(post.comments || []);
function toggleComments() {
showComments.value = !showComments.value;
}
function addComment() {
if (!newComment.value.trim()) return;
comments.value = [...comments.value, {
id: Date.now(),
author: "Anonymous",
text: newComment.value,
date: new Date().toLocaleDateString()
}];
newComment.value = "";
}
return { post, showComments, newComment, comments, toggleComments, addComment };
},
template: (ctx) => `
<article class="blog-post">
<header>
<h2>${ctx.post.title}</h2>
<div class="post-meta">
<span>By ${ctx.post.author}</span>
<span>${ctx.post.date}</span>
<span>${ctx.post.readTime} min read</span>
</div>
</header>
<div class="post-content">
<p>${ctx.post.excerpt}</p>
</div>
<footer>
<button @click="toggleComments">
${ctx.showComments.value ? 'Hide' : 'Show'} Comments (${ctx.comments.value.length})
</button>
</footer>
${ctx.showComments.value ? `
<div class="comments-section">
${ctx.comments.value.map(comment => `
<div key="${comment.id}" class="comment-wrapper" :comment="comment"></div>
`).join('')}
<div class="add-comment">
<textarea
placeholder="Write a comment..."
@input="(e) => newComment.value = e.target.value"
>${ctx.newComment.value}</textarea>
<button @click="addComment">Post Comment</button>
</div>
</div>
` : ''}
</article>
`,
children: {
".comment-wrapper": "Comment"
}
});
// Main Blog Component
app.component("Blog", {
setup({ signal }) {
const posts = signal([
{
id: 1,
title: "Getting Started with Eleva",
author: "John Doe",
date: "January 3, 2026",
readTime: 5,
excerpt: "Learn the basics of Eleva, a minimalist JavaScript framework that emphasizes simplicity and performance. In this guide, we'll cover components, signals, and the template system.",
comments: [
{ id: 1, author: "Jane", text: "Great introduction!", date: "January 3, 2026" }
]
},
{
id: 2,
title: "Building Reactive UIs",
author: "Jane Smith",
date: "January 2, 2026",
readTime: 8,
excerpt: "Dive deep into Eleva's signal-based reactivity system. Understand how signals work under the hood and how to use them effectively in your applications.",
comments: []
},
{
id: 3,
title: "Plugin Development Guide",
author: "John Doe",
date: "January 1, 2026",
readTime: 10,
excerpt: "Learn how to extend Eleva with custom plugins. We'll build a complete plugin from scratch, covering the plugin lifecycle and best practices.",
comments: [
{ id: 1, author: "Dev", text: "Very helpful!", date: "January 1, 2026" },
{ id: 2, author: "Alex", text: "Can you cover testing?", date: "January 2, 2026" }
]
}
]);
return { posts };
},
template: (ctx) => `
<div class="blog">
<header class="blog-header">
<h1>Eleva Blog</h1>
<p>Tutorials, guides, and best practices</p>
</header>
<div class="posts">
${ctx.posts.value.map(post => `
<div key="${post.id}" class="post-wrapper" :post="post"></div>
`).join('')}
</div>
</div>
`,
style: `
.blog { max-width: 800px; margin: 0 auto; padding: 20px; }
.blog-header { text-align: center; margin-bottom: 40px; padding-bottom: 20px; border-bottom: 1px solid #eee; }
.blog-header h1 { margin: 0 0 10px 0; }
.blog-header p { color: #666; margin: 0; }
.blog-post { background: white; border: 1px solid #eee; border-radius: 8px; padding: 25px; margin-bottom: 20px; }
.blog-post h2 { margin: 0 0 10px 0; }
.post-meta { display: flex; gap: 15px; color: #666; font-size: 14px; margin-bottom: 15px; }
.post-content { color: #444; line-height: 1.6; }
.blog-post footer { margin-top: 20px; padding-top: 15px; border-top: 1px solid #eee; }
.blog-post footer button { background: none; border: 1px solid #ddd; padding: 8px 16px; border-radius: 4px; cursor: pointer; }
.comments-section { margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee; }
.comment { background: #f8f9fa; padding: 15px; border-radius: 4px; margin-bottom: 10px; }
.comment-header { display: flex; justify-content: space-between; margin-bottom: 8px; }
.comment-header .date { color: #999; font-size: 12px; }
.comment p { margin: 0; }
.add-comment textarea { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 4px; resize: vertical; min-height: 80px; margin-bottom: 10px; }
.add-comment button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
`,
children: {
".post-wrapper": "BlogPost"
}
});
app.mount(document.getElementById("app"), "Blog");
Blog (parent)
├── BlogPost (child, multiple)
│ └── Comment (grandchild, multiple)
This demonstrates Eleva’s component composition:
Blog manages the list of postsBlogPost handles individual post display and commentsComment renders a single comment| ← Back to Apps | Previous: Weather Dashboard | Next: Custom Plugin Guide → |