add-component

star 0

Create or edit Flint Static UI components (TypeScript classes extending Component<T>). Use when building reusable server-rendered UI that appears across multiple pages via {{tag}} placeholders in templates.

NeoTech By NeoTech schedule Updated 2/25/2026

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 :::html block 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('&lt;script&gt;');
  });
});

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 effects
  • this.escapeHtml() on every user-supplied string
  • this.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 tagDefs exports by scanning src/components/do NOT edit tag-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, description power 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.ts with XSS escape test
  • Extends Component<T> with typed props interface
  • render() is pure — no side effects
  • All user text through this.escapeHtml()
  • tagDefs exported from component file; registry auto-discovers it
  • No hardcoded data in tagDefs.resolve — frontmatter drives data
  • TemplateContext extended 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:run passes
  • bun run build succeeds

References

  • references/base-class.mdComponent<T> API and inherited methods
  • references/tag-registration.md — How to register tags in the engine
  • references/built-in-components.md — All existing components for reference
Install via CLI
npx skills add https://github.com/NeoTech/Flint --skill add-component
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator