TypeScript Components
TypeScript components (.com.ts files) let you create dynamic, logic-driven components that run at build time. They have access to attributes and can generate HTML programmatically.
Quick Start
Create a .com.ts file:
// user-greeting.com.ts
const name = com.getAttribute("name") || "World";
com.innerHTML = `<h1>Hello, ${name}!</h1>`;
Use it with attributes:
<user-greeting name="Alice"></user-greeting>
<user-greeting name="Bob"></user-greeting>
<user-greeting></user-greeting>
Build output:
<h1>Hello, Alice!</h1>
<h1>Hello, Bob!</h1>
<h1>Hello, World!</h1>
How They Work
The com Variable
Every .com.ts file has access to a special com variable:
com.getAttribute("attr-name"); // Read attributes
com.innerHTML = "..."; // Set content (this is what's kept)
com.tagName; // Get element name
Only the innerHTML you set is included in the final output.
Build-Time Execution
.com.ts files run during the build process with Bun runtime, not in the browser:
Build Time:
1. Find <user-card name="John">
2. Execute user-card.com.ts
3. Capture com.innerHTML
4. Replace <user-card> with the innerHTML
Component Naming
Same rules as HTML components:
- End with
.com.ts - Use lowercase with hyphens
- Must contain at least one hyphen
Component Resolution
Tkeron looks for .com.ts files in this order:
- Same directory as the file using it
- Any subdirectory of
websrc/via glob search
You can organize components in subdirectories and they'll be found automatically.
IDE Support
Tkeron projects include tkeron.d.ts for IntelliSense:
declare const com: HTMLElement;
This enables autocomplete for com.getAttribute(), com.innerHTML, etc.
Examples
Attribute-Based Content
// user-badge.com.ts
const name = com.getAttribute("name") || "Guest";
const role = com.getAttribute("role") || "User";
com.innerHTML = `
<div class="badge">
<span class="badge-name">${name}</span>
<span class="badge-role">${role}</span>
</div>
`;
Dynamic Lists
// item-list.com.ts
const count = parseInt(com.getAttribute("count") || "3");
const items = [];
for (let i = 1; i <= count; i++) {
items.push(`<li>Item ${i}</li>`);
}
com.innerHTML = `<ul class="item-list">${items.join("")}</ul>`;
Conditional Rendering
// alert-box.com.ts
const type = com.getAttribute("type") || "info";
const message = com.getAttribute("message") || "";
const colors: Record<string, string> = {
info: "#3b82f6",
success: "#22c55e",
warning: "#f59e0b",
error: "#ef4444",
};
const color = colors[type] || colors.info;
com.innerHTML = `
<div style="padding: 1rem; background: ${color}20; border-left: 4px solid ${color};">
<strong>${type.toUpperCase()}</strong>
<p>${message}</p>
</div>
`;
Importing Modules
// product-card.com.ts
import { formatPrice } from "./utils";
const name = com.getAttribute("name") || "Product";
const price = parseFloat(com.getAttribute("price") || "0");
com.innerHTML = `
<div class="product-card">
<h3>${name}</h3>
<p class="price">${formatPrice(price)}</p>
</div>
`;
Using External APIs
// weather-widget.com.ts
const city = com.getAttribute("city") || "London";
const response = await fetch(`https://api.example.com/weather?city=${city}`);
const weather = await response.json();
com.innerHTML = `
<div class="weather-widget">
<h3>${city}</h3>
<div>${weather.temperature}°C — ${weather.condition}</div>
</div>
`;
Note: The API call happens once at build time. The output is static HTML.
HTML Template + TypeScript Logic
When both name.com.html and name.com.ts exist for the same component, tkeron combines them: the .com.html content is loaded as com.innerHTML before the .com.ts code runs. This lets you separate the HTML template from the logic.
How It Works
Build Time:
1. Find <user-card name="John">
2. Load user-card.com.html content into com.innerHTML
3. Execute user-card.com.ts (can read/modify the template)
4. Capture final com.innerHTML
5. Replace <user-card> with the result
Example: Card with Template
Template — product-card.com.html:
<div class="product-card">
<h3 class="product-name"></h3>
<p class="product-price"></p>
<span class="badge">New</span>
</div>
Logic — product-card.com.ts:
const name = com.getAttribute("name") || "Product";
const price = com.getAttribute("price") || "0";
const nameEl = com.querySelector(".product-name");
if (nameEl) nameEl.textContent = name;
const priceEl = com.querySelector(".product-price");
if (priceEl) priceEl.textContent = `$${price}`;
Usage:
<product-card name="Headphones" price="99"></product-card>
Output:
<div class="product-card">
<h3 class="product-name">Headphones</h3>
<p class="product-price">$99</p>
<span class="badge">New</span>
</div>
Why Use Templates?
- Separation of concerns — HTML structure in
.com.html, logic in.com.ts - Cleaner code — No more building HTML strings manually in TypeScript
- Easier styling — The HTML template is visible to your editor and CSS tools
- Team workflow — Designers edit HTML, developers edit TypeScript
Template Resolution
Templates follow the same resolution rules as components:
- Same directory as the
.com.tsfile (highest priority) - Any subdirectory of
websrc/via glob search
Limitations
- No event listeners — Lost in static output. Use regular
.tsfiles for interactivity - No DOM access beyond
com— Use pre-rendering for document-wide access - No reactive state — Components run once at build time
- No runtime data — Can't access
localStorage,window, etc.
Best Practices
✅ Validate attributes — Check types and allowed values
✅ Escape HTML — Prevent XSS with attribute content
✅ Use TypeScript types — Leverage full TS support
✅ Extract complex logic — Import from utility modules
✅ Use HTML templates — Separate structure from logic with .com.html + .com.ts
❌ Don't use for simple static content — Use HTML Components instead
Next Steps
- HTML Components — Simpler static components
- Markdown Components — Content in Markdown
- Pre-rendering — Document-wide transformations
- CLI Reference — Build options