> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pictory.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Scene Elements (Shapes & Text)

> Add decorative shape and text overlays to any scene — squares, circles, badges, arrows, stars, quotes, and standalone text that will not interrupt your AI voiceovers

This guide shows you how to add **shape** and **text** elements to your scenes. Elements are decorative overlays placed on top of a scene's background — for example a badge behind a number, an arrow pointing at a product, or a headline that does not affect your narration.

## What You'll Learn

<CardGroup cols={2}>
  <Card title="Shape Elements" icon="shapes">
    Add basic shapes (rectangle, circle, line) and 130+ library shapes
  </Card>

  <Card title="Text Elements" icon="font">
    Add decorative text overlays (heading, subheading, body)
  </Card>

  <Card title="Positioning" icon="up-down-left-right">
    Anchor elements with presets and size them as a fraction of the canvas
  </Card>

  <Card title="Styling" icon="palette">
    Control fill, stroke, font, color, alignment, and more
  </Card>
</CardGroup>

## Before You Begin

Make sure you have:

* A [Pictory API key](https://app.pictory.ai/api-access)
* Node.js or Python installed on your machine
* Basic understanding of [scene configuration](/guides/video-storyboard) in the Pictory API

<Note>
  Scene elements are processed by the avinya (v3) storyboard engine. Any scene that includes an `elements` array is automatically routed to v3 — you do not need to set `storyboardVersion` yourself.
</Note>

## How Scene Elements Work

Each scene accepts an optional `elements` array. Each entry is either a **shape** or a **text** element:

```jsonc theme={null}
"scenes": [
  {
    "story": "Your scene narration here.",
    "elements": [
      {
        "type": "shape",
        "name": "badge-3",
        "fill": "rgba(255,255,255,1)",
        "position": "top-left",
        "width": "20%"
      },
      {
        "type": "text",
        "text": "50% OFF",
        "textVariant": "heading",
        "position": "center"
      }
    ]
  }
]
```

* A maximum of **20 elements** per scene is allowed.
* Elements render on top of the scene background and any subtitles, in the order provided.

## Shape Elements

Set `type: "shape"` and a `name`. The shape's geometry is resolved automatically — you only provide color and position.

| Field          | Type   | Required | Description                                                                                                             |
| -------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------- |
| `type`         | string | Yes      | Must be `"shape"`                                                                                                       |
| `name`         | string | Yes      | Shape name — see [Shape gallery](#shape-gallery)                                                                        |
| `fill`         | string | No       | Fill color (`rgba(...)` or hex). Defaults to a neutral gray                                                             |
| `stroke`       | string | No       | Outline color (`rgba(...)` or hex)                                                                                      |
| `strokeWidth`  | number | No       | Outline thickness in pixels                                                                                             |
| `borderRadius` | number | No       | Corner radius — `rectangle` only                                                                                        |
| `position`     | string | No       | Anchor preset; defaults to `top-right`. Use `top`/`left` instead for explicit offsets — see [Positioning](#positioning) |

<Note>
  Only `type` and `name` are required. Everything else has a sensible default: no `position` → `top-right`, no `width` → 20% of the canvas, no `fill` → neutral gray. So `{ "type": "shape", "name": "rectangle" }` is a complete, valid element.
</Note>

#### Shape examples

```jsonc theme={null}
"elements": [
  // 1. Minimal — relies on every default (top-right, 20% wide, neutral gray)
  { "type": "shape", "name": "rectangle" },

  // 2. A fully styled rounded rectangle, centered
  {
    "type": "shape",
    "name": "rectangle",
    "fill": "rgba(255,0,0,1)",
    "stroke": "rgba(0,0,0,1)",
    "strokeWidth": 4,
    "borderRadius": 12,
    "position": "center",
    "width": "30%"
  },

  // 3. A featured library shape, recolored green
  { "type": "shape", "name": "badge-3", "fill": "rgba(20,180,90,1)", "position": "top-left", "width": "18%" },

  // 4. A featured shape with NO fill keeps its original colors
  { "type": "shape", "name": "badge-3", "position": "bottom-left", "width": "18%" }
]
```

<Note>
  Colors must be `rgba(r,g,b,a)` or hex (`#rrggbb`). Plain `rgb(...)` without an alpha channel is rejected.
</Note>

### Pick a Shape by Use Case

Not sure where to start? These are the most common pairings.

| You want to…                           | Try shape                         | Pair with                                           |
| -------------------------------------- | --------------------------------- | --------------------------------------------------- |
| Add a call-to-action button background | `rectangle` (with `borderRadius`) | `text` element on top (`textVariant: "body"`)       |
| Highlight a discount or price          | `badge-3` or `badge-5`            | `text` element with `textVariant: "heading"`        |
| Point at a product or feature          | `arrow-4` or `arrow-12`           | Place near the subject with `top`/`left` offsets    |
| Frame a testimonial or quote           | `quote-1` or `speech-bubble-2`    | `text` element with `textVariant: "body"`           |
| Mark "complete" or "approved"          | `checkmark-1`                     | Place top-right with default position               |
| Mark "wrong" or "do not do this"       | `cross-1`                         | Place top-right with default position               |
| Add a rating or emphasis               | `star-1` to `star-6`              | Repeat the same shape with different `left` offsets |
| Soft, modern background accent         | `blob-1` to `blob-9`              | Set low-opacity `fill` like `rgba(255,200,100,0.3)` |
| Underline a key word in a heading      | `line-1` to `line-17`             | Position below a `text` element                     |
| Add a divider between sections         | `line` (basic)                    | Center it, set `width: "60%"`                       |

### Shape Gallery

All 140 shape names available to the API. Type below to filter by name, then click any tile to copy.

<input
  type="search"
  placeholder="Filter shapes by name (e.g. arrow, badge, heart)…"
  aria-label="Filter shapes"
  onInput={(e) => {
const q = e.target.value.trim().toLowerCase();
document.querySelectorAll('[data-shape-tile]').forEach((tile) => {
  const name = tile.getAttribute('data-name') || '';
  tile.style.display = !q || name.includes(q) ? '' : 'none';
});
}}
  style={{
width: "100%",
padding: "0.6rem 0.9rem",
margin: "0 0 1rem",
fontSize: "0.95rem",
border: "1px solid var(--mintlify-color-border, #e5e5e5)",
borderRadius: "8px",
background: "var(--mintlify-color-background, #fff)",
color: "var(--mintlify-color-text, #111)",
boxSizing: "border-box"
}}
/>

export const shapeNames = ["rectangle", "circle", "line", "badge-1", "badge-2", "badge-3", "badge-4", "badge-5", "badge-6", "badge-7", "badge-8", "quote-1", "quote-2", "quote-3", "quote-4", "quote-5", "quote-6", "arrow-1", "arrow-2", "arrow-3", "arrow-4", "arrow-5", "arrow-6", "arrow-7", "arrow-8", "arrow-9", "arrow-10", "arrow-11", "arrow-12", "arrow-13", "arrow-14", "arrow-15", "arrow-16", "arrow-17", "arrow-18", "arrow-19", "geometry-1", "geometry-2", "geometry-3", "geometry-4", "geometry-5", "geometry-6", "geometry-7", "geometry-8", "geometry-10", "doodle-1", "doodle-2", "doodle-3", "doodle-4", "doodle-5", "doodle-6", "doodle-7", "doodle-8", "doodle-9", "doodle-10", "doodle-11", "doodle-12", "star-1", "star-2", "star-3", "star-4", "star-5", "star-6", "cross-1", "cross-2", "cross-3", "cross-4", "cross-5", "cross-6", "cross-7", "cross-8", "checkmark-1", "checkmark-2", "checkmark-3", "checkmark-4", "checkmark-5", "checkmark-6", "data-device-1", "data-device-2", "data-device-3", "data-device-4", "data-device-5", "data-device-6", "data-device-7", "data-device-8", "blob-1", "blob-2", "blob-3", "blob-4", "blob-5", "blob-6", "blob-7", "blob-8", "blob-9", "word-1", "word-2", "word-3", "word-4", "word-5", "word-6", "word-7", "word-8", "word-9", "line-1", "line-2", "line-3", "line-4", "line-5", "line-6", "line-7", "line-8", "line-9", "line-10", "line-11", "line-12", "line-13", "line-14", "line-15", "line-16", "line-17", "speech-bubble-2", "speech-bubble-3", "speech-bubble-4", "speech-bubble-5", "speech-bubble-6", "speech-bubble-7", "square-1", "square-2", "square-3", "square-4", "square-5", "heart-1", "heart-2", "heart-3", "thumbs-up", "gift", "leaves", "paperplane", "idea-bulb", "pill"];

export const tileStyle = {
  display: "flex",
  flexDirection: "column",
  alignItems: "center",
  justifyContent: "center",
  gap: "0.5rem",
  padding: "1rem 0.5rem",
  border: "1px solid var(--mintlify-color-border, #e5e5e5)",
  borderRadius: "8px",
  minHeight: "110px",
  cursor: "pointer",
  transition: "border-color 0.15s, background 0.15s"
};

export const imgStyle = {
  maxWidth: "56px",
  maxHeight: "40px",
  width: "auto",
  height: "auto",
  objectFit: "contain"
};

export const nameStyle = {
  fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
  fontSize: "0.78rem",
  textAlign: "center",
  wordBreak: "break-all",
  opacity: 0.85
};

export const gridStyle = {
  display: "grid",
  gridTemplateColumns: "repeat(auto-fill, minmax(120px, 1fr))",
  gap: "0.75rem",
  margin: "0 0 2rem"
};

<div
  style={gridStyle}
  onClick={(e) => {
const tile = e.target.closest('[data-shape-tile]');
if (!tile) return;
const name = tile.getAttribute('data-name');
if (navigator.clipboard) {
  navigator.clipboard.writeText(name);
  const orig = tile.style.background;
  tile.style.background = 'rgba(132, 44, 254, 0.12)';
  setTimeout(() => { tile.style.background = orig; }, 500);
}
}}
>
  {shapeNames.map((name) => (
      <div key={name} data-shape-tile data-name={name} style={tileStyle}>
        <img src={`/images/shapes/${name}.svg`} alt={name} style={imgStyle} />
        <span style={nameStyle}>{name}</span>
      </div>
    ))}
</div>

## Text Elements

Set `type: "text"` and a `text` string. Use `textVariant` for a quick decorative preset, or `style` for full control.

| Field                   | Type   | Required | Description                                                                                    |
| ----------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------- |
| `type`                  | string | Yes      | Must be `"text"`                                                                               |
| `text`                  | string | Yes      | The text to display (max 2000 chars)                                                           |
| `textVariant`           | string | No       | Decorative preset: `"heading"`, `"subheading"`, or `"body"`. Defaults to `"body"` if omitted   |
| `style`                 | object | No       | Font/color/alignment overrides (same as `subtitleStyle`)                                       |
| `styleId` / `styleName` | string | No       | Apply a saved text style                                                                       |
| `position`              | string | No       | Anchor preset; use `top`/`left` instead for explicit offsets — see [Positioning](#positioning) |

### Decorative text presets

`textVariant` seeds sensible defaults (font size, position, width). You can override any of them with `style` or `position`.

| Preset       | Default size | Default position | Default width |
| ------------ | ------------ | ---------------- | ------------- |
| `heading`    | 66           | center           | 0.9           |
| `subheading` | 42           | top-center       | 0.9           |
| `body`       | 20           | center           | 0.37          |

<Note>
  Default text positions are kept away from the bottom of the scene so decorative text does not overlap burned-in subtitles. If you want text at the bottom, set `position` explicitly (e.g. `"bottom-center"`).
</Note>

#### Text examples

```jsonc theme={null}
"elements": [
  // 1. Bare text — defaults to the "body" preset
  { "type": "text", "text": "Limited time only" },

  // 2. A big centered headline
  { "type": "text", "text": "50% OFF", "textVariant": "heading" },

  // 3. A subheading, pinned to the top
  { "type": "text", "text": "Summer sale ends Sunday", "textVariant": "subheading" },

  // 4. Body text placed explicitly via offsets
  { "type": "text", "text": "Terms apply", "textVariant": "body", "top": "70%", "left": "10%" }
]
```

### Text style overrides

The `style` object accepts the same fields as `subtitleStyle`, including:

| Field             | Example                 | Description                                               |
| ----------------- | ----------------------- | --------------------------------------------------------- |
| `fontFamily`      | `"Plus Jakarta Sans"`   | Font name                                                 |
| `fontSize`        | `48`                    | Font size in points                                       |
| `color`           | `"rgba(255,255,255,1)"` | Text color                                                |
| `backgroundColor` | `"rgba(0,0,0,0.35)"`    | Text background color                                     |
| `alignment`       | `"center"`              | `left`, `center`, or `right`                              |
| `case`            | `"uppercase"`           | `uppercase`, `lowercase`, `capitalize`, `smallcapitalize` |
| `decorations`     | `["bold"]`              | `bold`, `underline`, `italics`, `linethrough`             |

For example, a fully custom-styled headline:

```jsonc theme={null}
{
  "type": "text",
  "text": "Big news",
  "textVariant": "heading",
  "position": "center",
  "style": {
    "fontFamily": "Plus Jakarta Sans",
    "fontSize": 72,
    "color": "rgba(255,255,255,1)",
    "backgroundColor": "rgba(0,0,0,0.35)",
    "alignment": "center",
    "case": "uppercase",
    "decorations": ["bold"]
  }
}
```

## Positioning

Position an element in one of two ways: a `position` anchor **or** explicit `top`/`left` offsets. Use one or the other, not both. `width` is a size and can be combined with either. All fields are optional and live directly on the element.

| Field      | Type   | Description                                                                         |
| ---------- | ------ | ----------------------------------------------------------------------------------- |
| `position` | string | Anchor region (see below). Cannot be combined with `top`/`left`                     |
| `width`    | string | Element width as a percentage of the canvas, e.g. `"20%"`                           |
| `top`      | string | Vertical offset as a percentage, e.g. `"10%"`. Cannot be combined with `position`   |
| `left`     | string | Horizontal offset as a percentage, e.g. `"10%"`. Cannot be combined with `position` |

Valid `position` values:

`top-left`, `top-center`, `top-right`, `center-left`, `center`, `center-right`, `bottom-left`, `bottom-center`, `bottom-right`

<Note>
  `center` means the middle of the scene. `center-center` is also accepted and means the same thing.
</Note>

<Note>
  Use either `position` or `top`/`left`, not both — sending both is rejected. For a quick anchor, use `position` (a shape with no `position` defaults to `top-right`). For fine placement, use `top`/`left` instead of a preset.
</Note>

#### Positioning examples

```jsonc theme={null}
"elements": [
  // Preset anchors — placed at three corners/center of the scene
  { "type": "shape", "name": "circle", "fill": "rgba(255,0,0,1)", "position": "top-center", "width": "12%" },
  { "type": "shape", "name": "circle", "fill": "rgba(0,255,0,1)", "position": "center", "width": "12%" },
  { "type": "shape", "name": "circle", "fill": "rgba(0,0,255,1)", "position": "bottom-right", "width": "12%" },

  // Explicit offsets — use top/left instead of a position preset for fine placement
  { "type": "shape", "name": "rectangle", "fill": "rgba(255,87,51,1)", "top": "15%", "left": "10%", "width": "25%" }
]
```

### Multiple elements per scene

A single scene can mix shapes and text (up to 20 elements). They render in the order provided, on top of the background and subtitles.

```jsonc theme={null}
{
  "story": "Introducing our biggest sale of the year.",
  "elements": [
    { "type": "shape", "name": "pill", "fill": "rgba(0,128,255,1)", "position": "center", "width": "40%" },
    { "type": "text", "text": "MEGA SALE", "textVariant": "heading", "position": "center" },
    { "type": "shape", "name": "star-1", "fill": "rgba(245,200,60,1)", "position": "top-right", "width": "12%" }
  ]
}
```

## Complete Example

<CodeGroup>
  ```javascript Node.js theme={null}
  import axios from "axios";

  const API_BASE_URL = "https://api.pictory.ai/pictoryapis";
  const API_KEY = "YOUR_API_KEY";

  async function createVideoWithElements() {
    const response = await axios.post(
      `${API_BASE_URL}/v2/video/storyboard`,
      {
        videoName: "video_with_scene_elements",
        scenes: [
          {
            story: "Introducing our biggest sale of the year.",
            elements: [
              // A library badge in the top-left corner
              {
                type: "shape",
                name: "badge-3",
                fill: "rgba(255,87,51,1)",
                position: "top-left",
                width: "18%",
              },
              // A bold decorative heading
              {
                type: "text",
                text: "50% OFF",
                textVariant: "heading",
                style: { color: "rgba(255,255,255,1)", case: "uppercase", decorations: ["bold"] },
                position: "center",
              },
            ],
          },
          {
            story: "Tap the link below to claim your discount today.",
            elements: [
              // A basic rounded rectangle as a call-to-action background
              {
                type: "shape",
                name: "rectangle",
                fill: "rgba(0,128,255,1)",
                borderRadius: 12,
                position: "bottom-center",
                width: "50%",
              },
              // Body text on top of it
              {
                type: "text",
                text: "Claim your discount",
                textVariant: "body",
                position: "bottom-center",
                width: "45%",
              },
            ],
          },
        ],
      },
      {
        headers: {
          "Content-Type": "application/json",
          Authorization: API_KEY,
        },
      }
    );

    console.log("✓ Storyboard creation started!");
    console.log("Job ID:", response.data.data.jobId);
  }

  createVideoWithElements();
  ```

  ```python Python theme={null}
  import requests

  API_BASE_URL = "https://api.pictory.ai/pictoryapis"
  API_KEY = "YOUR_API_KEY"

  def create_video_with_elements():
      response = requests.post(
          f"{API_BASE_URL}/v2/video/storyboard",
          headers={"Content-Type": "application/json", "Authorization": API_KEY},
          json={
              "videoName": "video_with_scene_elements",
              "scenes": [
                  {
                      "story": "Introducing our biggest sale of the year.",
                      "elements": [
                          {
                              "type": "shape",
                              "name": "badge-3",
                              "fill": "rgba(255,87,51,1)",
                              "position": "top-left",
                              "width": "18%",
                          },
                          {
                              "type": "text",
                              "text": "50% OFF",
                              "textVariant": "heading",
                              "style": {"color": "rgba(255,255,255,1)", "case": "uppercase", "decorations": ["bold"]},
                              "position": "center",
                          },
                      ],
                  },
                  {
                      "story": "Tap the link below to claim your discount today.",
                      "elements": [
                          {
                              "type": "shape",
                              "name": "rectangle",
                              "fill": "rgba(0,128,255,1)",
                              "borderRadius": 12,
                              "position": "bottom-center",
                              "width": "50%",
                          },
                          {
                              "type": "text",
                              "text": "Claim your discount",
                              "textVariant": "body",
                              "position": "bottom-center",
                              "width": "45%",
                          },
                      ],
                  },
              ],
          },
      )

      print("✓ Storyboard creation started!")
      print("Job ID:", response.json()["data"]["jobId"])

  create_video_with_elements()
  ```
</CodeGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Scene Transitions" icon="right-left" href="/guides/advanced-features/transitions">
    Add smooth transitions between scenes
  </Card>

  <Card title="Smart Layouts" icon="table-layout" href="/guides/smart-layouts-and-subtitles/smart-layouts">
    Use pre-designed scene layouts
  </Card>
</CardGroup>
