# Custom element quickstart

Write a Node subclass with @vidova/2d, implement atTime, then place it with component create and timeline_edit addClip.

This path assumes Vidova is running, Local MCP is on, and a project is open in the editor. Install steps are on the [MCP overview](/docs/mcp).

## 1. Write the class

The exported class name must match `componentName`. `atTime(time, duration)` is the timeline entry point. It sets the element for `time` seconds into the clip. Details: [Animation](/docs/custom-elements/animation).

```tsx
import { Node, NodeProps, Txt, signal, initial, colorSignal } from '@vidova/2d';
import {
  SignalValue,
  SimpleSignal,
  ColorSignal,
  PossibleColor,
  clamp,
  easeOutCubic,
} from '@vidova/core';

export interface HelloTitleProps extends NodeProps {
  label?: SignalValue<string>;
  textColor?: SignalValue<PossibleColor>;
  textSize?: SignalValue<number>;
}

export class HelloTitle extends Node {
  @initial('Hello')
  @signal()
  public declare readonly label: SimpleSignal<string, this>;

  @initial('#ffffff')
  @colorSignal()
  public declare readonly textColor: ColorSignal<this>;

  @initial(48)
  @signal()
  public declare readonly textSize: SimpleSignal<number, this>;

  public constructor(props?: HelloTitleProps) {
    super({ ...props });
    this.add(
      <Txt
        text={() => this.label()}
        fill={() => this.textColor()}
        fontSize={() => this.textSize()}
        fontFamily="Inter Variable"
        fontWeight={600}
      />,
    );
  }

  /** Clip length when nothing gives one, such as a thumbnail. */
  public readonly defaultDuration = 3;

  public atTime(time: number): void {
    this.opacity(easeOutCubic(clamp(0, 1, time / 0.4)));
  }
}
```

Use the exact `fontFamily` string. `Inter Variable` is the default UI font. Code text uses `Fira Code Variable`.

## 2. Create the asset

Call [component](/docs/mcp/tools/component) with `action` `create`:

```json
{
  "action": "create",
  "name": "Hello title",
  "componentName": "HelloTitle",
  "code": "<the TSX above>"
}
```

The result includes `assetId`. If the tool reports `FAILED TO RENDER`, fix the class before you place it.

## 3. Place it on the timeline

Call [timeline_edit](/docs/mcp/tools/timeline_edit) with `action` `addClip`, `type` `component`, and that `assetId`. Never `type=text`.

## 4. Capture a preview

Call `preview` with `action` `captureFrame` at a timecode where the clip is visible. Confirm the text is on screen and fully inside the frame.

A catalog template is faster when one already covers the design. Use `createFromTemplate` instead of `create`. Full props and methods: [Authoring](/docs/custom-elements/authoring).
