name: add-component
description: Create or edit Flint Static UI components (TypeScript classes extending Component). Use when building reusable server-rendered UI that appears across multiple pages via {{tag}} placeholders in templates.
Add / Edit a Component
Create or modify reusable UI components in src/components/. Components are TypeScript classes that return HTML strings, invoked via {{tag}} placeholders in templates.
Trigger Phrases
- "Create a [name] component"
- "Add a reusable [card / banner / alert / grid] UI element"
- "Build a [name] widget that appears on multiple pages"
- "I need a {{tag}} that renders [description]"
- "Add a [testimonial / pricing / FAQ] section component"
- "Make the [name] component show [new data]"
- "Extend the [existing component] to support [variant]"
When to Use
- Building reusable UI that appears on multiple pages (nav, footer, card, alert)
- Rendering logic needs loops, conditionals, or computed values
- HTML output needs
escapeHtml()for safety - UI pattern needs unit testing
When NOT to Use
- Page-specific content → use Markdown in
content/ - Page-specific HTML → use
:::htmlblock in content - Page layout structure → use a template
- Client-side interactivity → use
src/client/*.ts
Procedure
1. Write tests first
Create src/components/<name>.test.ts:
import { describe, it, expect } from 'bun:test';
import { MyComponent } from './<name>.js';
describe('MyComponent', () => {
it('should render with required props', () => {
const html = MyComponent.render({ /* props */ });
expect(html).toContain('<expected-element');
});
it('should escape user content', () => {
const html = MyComponent.render({
text: '<script>alert("xss")</script>',
});
expect(html).not.toContain('<script>');
expect(html).toContain('<script>');
});
});
2. Implement the component
Create src/components/<name>.ts:
import { Component, type ComponentProps } from './component.js';
export interface MyComponentProps extends ComponentProps {
// typed props here
}
export class MyComponent extends Component<MyComponentProps> {
render(): string {
return `<div class="...">
${this.escapeHtml(this.props.someField)}
</div>`;
}
}
Rules:
- Extend
Component<T>with a typed props interface render()returns pure HTML string — no side effectsthis.escapeHtml()on every user-supplied stringthis.classNames()for conditional CSS classes- Tailwind utility classes only — no
<style>tags
3. Export tagDefs from the component file
At the bottom of src/components/<name>.ts, add:
import type { TagDef } from '../templates/tag-registry.js';
export const tagDefs: TagDef[] = [
{
tag: 'my-tag',
label: 'My Tag',
icon: '🔧',
description: 'One-line description of this component.',
resolve: (ctx) => {
const data = ctx.frontmatter['MyData'] as MyProps | undefined;
if (!data) return '';
return MyComponent.render(data);
},
},
];
- The tag registry auto-discovers
tagDefsexports by scanningsrc/components/— do NOT edittag-engine.ts - Return
''when frontmatter data is missing so{{#if my-tag}}suppresses the block - Never hardcode props — content files drive data into components via frontmatter
label,icon,descriptionpower the manager's component browser
4. Extend TemplateContext if needed
If the component needs data not in TemplateContext (src/templates/template-registry.ts), add the field and populate it in src/core/builder.ts. Most components should read from ctx.frontmatter directly — only add TemplateContext fields for site-wide data (navigation, siteLabels).
5. Add component data to content frontmatter
The content file should provide structured YAML matching the component's props:
---
MyData:
fieldA: value
fieldB: value
---
The tagDefs resolve function reads this from ctx.frontmatter and passes it to the component. Content authors control the data; the component controls the presentation.
5. Use in templates
Add {{my-tag}} to themes/default/templates/*.html (or the active theme's template). Guard optional tags:
{{#if my-tag}}{{my-tag}}{{/if}}
6. Run tests and build
bun run test:run
bun run build
Checklist
- Tests in
src/components/<name>.test.tswith XSS escape test - Extends
Component<T>with typed props interface -
render()is pure — no side effects - All user text through
this.escapeHtml() -
tagDefsexported from component file; registry auto-discovers it - No hardcoded data in
tagDefs.resolve— frontmatter drives data -
TemplateContextextended only if site-wide data needed - Content file has structured YAML matching component props
- Tag placed in template with
{{#if}}guard if optional -
bun run test:runpasses -
bun run buildsucceeds
References
references/base-class.md—Component<T>API and inherited methodsreferences/tag-registration.md— How to register tags in the enginereferences/built-in-components.md— All existing components for reference