# Slide Templates & Layouts: How Designs Get Chosen

> Presenton ships with named design templates (general, pitch-deck, Education, Code, etc.) and per-slide layouts. The LLM picks a layout index for each slide based on content rules baked into the system prompt.

- Repository: presenton/presenton
- GitHub: https://github.com/presenton/presenton
- Human wiki: https://grok-wiki.com/public/wiki/presenton-presenton-f6685dc028cc
- Complete Markdown: https://grok-wiki.com/public/wiki/presenton-presenton-f6685dc028cc/llms-full.txt

## Source Files

- `servers/nextjs/app/presentation-templates`
- `servers/fastapi/constants/presentation.py`
- `servers/fastapi/utils/ppt_utils.py`
- `servers/fastapi/utils/theme_utils.py`
- `servers/fastapi/api/v1/ppt/endpoints/theme_generate.py`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [servers/nextjs/app/presentation-templates/index.tsx](servers/nextjs/app/presentation-templates/index.tsx)
- [servers/nextjs/app/presentation-templates/defaultSchemes.ts](servers/nextjs/app/presentation-templates/defaultSchemes.ts)
- [servers/nextjs/app/presentation-templates/general/IntroSlideLayout.tsx](servers/nextjs/app/presentation-templates/general/IntroSlideLayout.tsx)
- [servers/nextjs/app/presentation-templates/general/settings.json](servers/nextjs/app/presentation-templates/general/settings.json)
- [servers/nextjs/app/presentation-templates/neo-modern/settings.json](servers/nextjs/app/presentation-templates/neo-modern/settings.json)
- [servers/nextjs/app/presentation-templates/pitch-deck/settings.json](servers/nextjs/app/presentation-templates/pitch-deck/settings.json)
- [servers/fastapi/constants/presentation.py](servers/fastapi/constants/presentation.py)
- [servers/fastapi/templates/presentation_layout.py](servers/fastapi/templates/presentation_layout.py)
- [servers/fastapi/templates/get_layout_by_name.py](servers/fastapi/templates/get_layout_by_name.py)
- [servers/fastapi/templates/handler.py](servers/fastapi/templates/handler.py)
- [servers/fastapi/templates/providers.py](servers/fastapi/templates/providers.py)
- [servers/fastapi/utils/ppt_utils.py](servers/fastapi/utils/ppt_utils.py)
- [servers/fastapi/utils/theme_utils.py](servers/fastapi/utils/theme_utils.py)
- [servers/fastapi/utils/llm_calls/generate_presentation_structure.py](servers/fastapi/utils/llm_calls/generate_presentation_structure.py)
- [servers/fastapi/utils/llm_calls/generate_slide_content.py](servers/fastapi/utils/llm_calls/generate_slide_content.py)
- [servers/fastapi/api/v1/ppt/endpoints/theme_generate.py](servers/fastapi/api/v1/ppt/endpoints/theme_generate.py)
</details>

# Slide Templates & Layouts: How Designs Get Chosen

Presenton ships with a library of named design template groups — General, Modern, Standard, Swift, Code, Education, Product Overview, Report, Pitch Deck, and their "Neo" variants — each containing a fixed set of per-slide layouts. When the LLM generates a presentation, it receives a plain-text menu of available slide layouts and returns a list of integer indices, one per slide, telling the renderer which React component to use for each slide. The color palette is then generated independently and applied on top of whichever layout was chosen.

Understanding this system is essential for anyone adding new layouts, debugging unexpected slide appearances, or extending the template library. The design decision — layout index selection — happens entirely inside a structured LLM call, driven by two distinct system prompts. The React components on the Next.js side and the FastAPI backend on the Python side share layout metadata through a JSON schema that is served at runtime via the export service.

---

## Template Groups

Presenton organizes layouts into named template groups. Each group is a folder under `servers/nextjs/app/presentation-templates/` and has a `settings.json` that declares metadata for the group.

**Verified template group directories:**

| Group ID | Display Name | `default` in settings | Notes |
|---|---|---|---|
| `general` | General | `true` | Fallback/default group |
| `modern` | Modern | `false` | |
| `standard` | Standard | `false` | |
| `swift` | Swift | `false` | |
| `code` | Code | `false` | Code/API-focused layouts |
| `education` | Education | `false` | |
| `product-overview` | Product Overview | `false` | |
| `report` | Report | `false` | |
| `pitch-deck` | Pitch Deck | `false` | |
| `neo-general` | Neo General | `false` | Refreshed general |
| `neo-standard` | Neo Standard | `false` | |
| `neo-modern` | Neo Modern | `false` | |
| `neo-swift` | Neo Swift | `false` | |

Each `settings.json` contains a short `description`, an `ordered` boolean (whether layouts have a fixed sequence), and an `icon_weight` hint used when rendering icons across slides.

Example — `general/settings.json`:
```json
{
  "description": "General purpose layouts for common presentation elements",
  "ordered": false,
  "default": true,
  "icon_weight": "regular"
}
```

Example — `neo-modern/settings.json`:
```json
{
  "description": "New modern purpose layouts for common presentation elements",
  "ordered": false,
  "default": false,
  "icon_weight": "regular"
}
```

Sources: [servers/nextjs/app/presentation-templates/general/settings.json](servers/nextjs/app/presentation-templates/general/settings.json), [servers/nextjs/app/presentation-templates/neo-modern/settings.json](servers/nextjs/app/presentation-templates/neo-modern/settings.json)

The FastAPI layer exposes four template names as "default" templates that do not require a database record:

```python
DEFAULT_TEMPLATES = ["general", "modern", "standard", "swift"]
```

Sources: [servers/fastapi/constants/presentation.py:1](servers/fastapi/constants/presentation.py)

Custom user-created templates are stored in the database and referenced with a `custom-<uuid>` ID prefix.

---

## How Layouts Are Defined (Next.js Side)

Each slide layout lives in its own `.tsx` file inside a group subdirectory. Every layout file exports four named constants that the registry uses to describe the layout to the LLM:

| Export | Purpose |
|---|---|
| `layoutId` | Stable string ID, e.g. `'general-intro-slide'` |
| `layoutName` | Human-readable name, e.g. `'Intro Slide'` |
| `layoutDescription` | One-sentence description fed to the LLM |
| `Schema` | Zod schema describing the slide's data fields |

Example from `general/IntroSlideLayout.tsx`:

```tsx
export const layoutId = 'general-intro-slide'
export const layoutName = 'Intro Slide'
export const layoutDescription = 'A clean slide layout with title, description text, presenter info, and a supporting image.'

const introSlideSchema = z.object({
    title: z.string().min(3).max(40)...
    description: z.string().min(10).max(150)...
    image: ImageSchema...
})
export const Schema = introSlideSchema
```

Sources: [servers/nextjs/app/presentation-templates/general/IntroSlideLayout.tsx:4-26](servers/nextjs/app/presentation-templates/general/IntroSlideLayout.tsx)

Shared image and icon schemas are defined in `defaultSchemes.ts`:

```ts
export const ImageSchema = z.object({
    __image_url__: z.url()...,
    __image_prompt__: z.string().min(10).max(50)...
})
export const IconSchema = z.object({
    __icon_url__: z.string()...,
    __icon_query__: z.string().min(5).max(20)...
})
```

Sources: [servers/nextjs/app/presentation-templates/defaultSchemes.ts:1-20](servers/nextjs/app/presentation-templates/defaultSchemes.ts)

The `__image_url__`, `__icon_url__`, `__image_prompt__`, and `__icon_query__` field names are reserved. The FastAPI backend strips or normalizes them before and after LLM calls.

---

## The Central Registry (index.tsx)

All layouts from every group are imported and assembled in `servers/nextjs/app/presentation-templates/index.tsx`. It exports three things:

1. **Per-group arrays** such as `generalTemplates`, `codeTemplates`, `pitchDeckTemplates`, etc.
2. **`allLayouts`** — a flat array combining all templates in a specific priority order (neo variants first, then classic, then domain-specific groups).
3. **`templates`** — the typed `TemplateLayoutsWithSettings[]` array that pairs each group's layouts with its settings metadata for the UI.

```tsx
export const allLayouts: TemplateWithData[] = [
    ...neoGeneralTemplates,
    ...neoModernTemplates,
    ...neoStandardTemplates,
    ...neoSwiftTemplates,
    ...generalTemplates,
    ...modernTemplates,
    ...standardTemplates,
    ...swiftTemplates,
    ...codeTemplates,
    ...educationTemplates,
    ...productOverviewTemplates,
    ...reportTemplates,
    ...pitchDeckTemplates,
];
```

Sources: [servers/nextjs/app/presentation-templates/index.tsx:498-512](servers/nextjs/app/presentation-templates/index.tsx)

Each entry is created with `createTemplateEntry(Component, Schema, id, name, description, groupId, fileName)`. The group-to-settings wiring in `templates` gives the UI everything it needs to render a template picker:

```tsx
export const templates: TemplateLayoutsWithSettings[] = [
    { id: "general", name: "General", description: generalSettings.description,
      settings: generalSettings, layouts: generalTemplates },
    { id: "pitch-deck", name: "Pitch Deck", ...},
    ...
]
```

Sources: [servers/nextjs/app/presentation-templates/index.tsx:517-610](servers/nextjs/app/presentation-templates/index.tsx)

---

## How the FastAPI Backend Loads Layouts

When a presentation is generated the backend needs the template's layout metadata (id, name, description, and Zod-derived JSON schema for each slide). The function `get_layout_by_name` in `servers/fastapi/templates/get_layout_by_name.py` resolves a template group name to a `PresentationLayoutModel`.

**Resolution order:**

1. **Primary:** calls the export service's `extract_schema` endpoint at `http://localhost/schema?group=<name>`. The export service renders the Next.js page and extracts the schema from the React component tree.
2. **Fallback:** if the primary fails (older export runtimes), calls `http://localhost/api/template?group=<name>`.
3. **Local settings overlay:** reads `<layout_name>/settings.json` directly from the filesystem to apply the `icon_weight` setting accurately, since the export service may not preserve it.

```python
async def get_layout_by_name(layout_name: str) -> PresentationLayoutModel:
    ...
    local_settings = _read_builtin_template_settings(layout_name)
    if local_settings:
        local_icon_weight = extract_icon_weight_from_settings(local_settings)
        schema_payload["icon_weight"] = local_icon_weight
    ...
    return PresentationLayoutModel(**schema_payload)
```

Sources: [servers/fastapi/templates/get_layout_by_name.py:125-224](servers/fastapi/templates/get_layout_by_name.py)

The `PresentationLayoutModel` Pydantic model captures: `name`, `ordered`, `icon_weight`, and a list of `SlideLayoutModel` entries (each with `id`, `name`, `description`, `json_schema`).

Its `to_string()` method serializes the layout for the LLM:

```python
def to_string(self) -> str:
    message = "## Presentation Layout\n\n"
    for index, slide in enumerate(self.slides):
        message += f"### Slide Layout: {index}\n"
        message += f"- Name: {slide.name or slide.json_schema.get('title')}\n"
        message += f"- Description: {slide.description}\n\n"
    return message
```

Sources: [servers/fastapi/templates/presentation_layout.py:45-51](servers/fastapi/templates/presentation_layout.py)

This plain-text representation is what the LLM actually reads when choosing layouts.

---

## How the LLM Picks Layout Indices

Layout selection is a dedicated LLM call in `servers/fastapi/utils/llm_calls/generate_presentation_structure.py`. Two system prompts are used depending on the input mode:

### Standard Mode — Content-Driven Selection

Used for normal text-based input. The system prompt is `GET_MESSAGES_SYSTEM_PROMPT`:

```
You're a professional presentation designer with creative freedom to design engaging presentations.

# Layout Selection Guidelines
1. Content-driven choices: Let the slide's purpose guide layout selection
   - Opening/closing → Title layouts
   - Processes/workflows → Visual process layouts
   - Comparisons/contrasts → Side-by-side layouts
   - Data/metrics → Chart/graph layouts
   - Concepts/ideas → Image + text layouts
   - Key insights → Emphasis layouts

2. Visual variety: Aim for diverse slide layouts across the presentation.
   - Don't use same layout for multiple slides unless necessary.
   - Adjacent slide layouts should be different unless instructed/necessary otherwise.

4. Table of contents:
   - Must only use table of contents layout if slide content contains table of contents.
```

The user message is the `presentation_layout.to_string()` (slide index list with names and descriptions) followed by the presentation outline content.

Sources: [servers/fastapi/utils/llm_calls/generate_presentation_structure.py:60-118](servers/fastapi/utils/llm_calls/generate_presentation_structure.py)

### Markdown/Slide Mode — Rule-Based Selection

Used when the input is pre-structured slide markdown. The system prompt is `STRUCTURE_FROM_SLIDES_MARKDOWN_SYSTEM_PROMPT` with stricter content-matching rules:

```
# Selection Rules
- If content contains table, then select either table layout or graph layout.
- Don't select layout with image unless content contains image.
- Don't select table layout if content does not contain table.
- You are allowed to select same layout for multiple slides.

# Graph Layout Selection Rules
- Must only select a layout with chart if the content contains table with numeric data.
- Must select a layout that supports n-1 charts for n columns.
- Must prioritize layouts that support multiple charts.
```

In this mode, `presentation_layout.to_string(with_schema=True)` is called — passing the full JSON schema to the LLM, not just names and descriptions.

Sources: [servers/fastapi/utils/llm_calls/generate_presentation_structure.py:16-57](servers/fastapi/utils/llm_calls/generate_presentation_structure.py), [servers/fastapi/utils/llm_calls/generate_presentation_structure.py:121-132](servers/fastapi/utils/llm_calls/generate_presentation_structure.py)

### Output Format

Both prompts ask the LLM to respond with a JSON array of integers: one layout index per slide, referencing the position in the template group's ordered layout list.

```
# Output Rules:
- One layout index for each slide.
- Example: [0, 1, 2, 3, 4]
```

The response is validated against a dynamically generated Pydantic model (`get_presentation_structure_model_with_n_slides`) that enforces exactly N integers.

---

## Layout Selection Flow (End to End)

```text
User picks template group (e.g. "general")
         │
         ▼
FastAPI calls get_layout_by_name("general")
  → extract_schema from export service
  → reads settings.json for icon_weight
  → returns PresentationLayoutModel
    (ordered list of SlideLayoutModels with ids, names, descriptions, schemas)
         │
         ▼
generate_presentation_structure(outline, layout, instructions)
  → to_string() → plain text: "Slide Layout: 0\n- Name: Intro Slide\n..."
  → LLM receives: layout menu + slide outlines
  → LLM returns: [0, 3, 1, 5, 2, ...]  (one index per slide)
         │
         ▼
PresentationStructureModel stores index list
         │
         ▼
For each slide: look up SlideLayoutModel at chosen index
  → extract json_schema
  → generate_slide_content fills in structured data
         │
         ▼
Next.js renders: layout React component + structured data + theme colors
```

---

## Regex-Based Layout Fallback (ppt_utils.py)

When a specific layout type must be guaranteed regardless of what the LLM chose — for example, always finding a "Table of Contents" layout for the agenda slide — `servers/fastapi/utils/ppt_utils.py` provides `find_slide_layout_index_by_regex` and `select_toc_or_list_slide_layout_index`.

These functions search the layout list by scanning each slide's `id`, `name`, `description`, and `json_schema.title` against regex patterns:

```python
def find_slide_layout_index_by_regex(layout, patterns):
    def _find_index(pattern):
        regex = re.compile(pattern, re.IGNORECASE)
        for index, slide_layout in enumerate(layout.slides):
            candidates = [
                slide_layout.id or "",
                (slide_layout.name or ""),
                (slide_layout.description or ""),
                (slide_layout.json_schema.get("title") if slide_layout.json_schema else ""),
            ]
            for text in candidates:
                if text and regex.search(text):
                    return index
        return -1
    ...
```

For TOC detection, the patterns tried (in priority order) are: `table of contents`, `agenda`, `contents`, `outline`, `index`, `toc`. If none match, it falls back to bullet-list patterns like `bullet list`, `numbered list`, `list`.

Sources: [servers/fastapi/utils/ppt_utils.py:34-82](servers/fastapi/utils/ppt_utils.py)

---

## Color Theme Generation (Independent of Layout)

The layout choice and the color theme are completely independent. After layouts are selected, the endpoint `POST /theme/generate` in `servers/fastapi/api/v1/ppt/endpoints/theme_generate.py` generates a color palette.

All color work happens in the OKLCH perceptual color space (`theme_utils.py`). Given an optional set of seed colors (primary, background, accent 1, accent 2, text 1, text 2), the system:

1. Generates a random primary color if none is provided.
2. Generates a background color with at least **6:1 WCAG contrast** against the primary, retrying up to 200 times.
3. Derives accent colors at 90-degree hue intervals.
4. Derives text colors with at least 6:1 contrast against their base.
5. Produces ten lightness-scale variants of the primary, background, and accent colors.

The `ThemeData` response includes named CSS variables (`primary`, `background`, `card`, `stroke`, `background_text`, `primary_text`, `graph_0` through `graph_9`) which the Next.js templates consume via CSS custom properties like `var(--background-color)`.

Sources: [servers/fastapi/utils/theme_utils.py:179-191](servers/fastapi/utils/theme_utils.py), [servers/fastapi/api/v1/ppt/endpoints/theme_generate.py:25-74](servers/fastapi/api/v1/ppt/endpoints/theme_generate.py)

---

## Per-Slide Content Generation

Once a layout index is selected for a slide, `generate_slide_content.py` fills the slide's Zod-derived JSON schema with real content. The LLM receives the slide's raw outline text and the schema (as a JSON literal in the system prompt), and must return a valid JSON object matching it.

Special fields are excluded from the schema sent to the LLM (`__image_url__`, `__icon_url__`) so the LLM never writes image URLs directly. Image URLs are resolved separately by an image service; the LLM writes only `__image_prompt__` and `__icon_query__` hints.

```python
response_schema = remove_fields_from_schema(
    slide_layout.json_schema, ["__image_url__", "__icon_url__"]
)
```

Sources: [servers/fastapi/utils/llm_calls/generate_slide_content.py:172-176](servers/fastapi/utils/llm_calls/generate_slide_content.py)

---

## Adding a New Layout (Developer Guide)

The `index.tsx` file contains explicit `TODO` step markers. The workflow is:

1. **Step 1** — Create the `.tsx` file in the target group directory, exporting `layoutId`, `layoutName`, `layoutDescription`, and `Schema`.
2. **Step 2** — Import the layout (component, schema, id, name, description) at the top of `index.tsx`.
3. **Step 3** — Add a `createTemplateEntry(...)` call to the matching group array.
4. **Step 4/5** — The group array is already spread into `allLayouts` and `templates`; no additional wiring is needed unless creating a wholly new group.

When a new group is needed, a `settings.json` must also be created and imported, and a new entry added to both `allLayouts` and the `templates` array.

Sources: [servers/nextjs/app/presentation-templates/index.tsx:4-6](servers/nextjs/app/presentation-templates/index.tsx), [servers/nextjs/app/presentation-templates/index.tsx:239-260](servers/nextjs/app/presentation-templates/index.tsx)

---

## Summary

Presenton's template system works in three independent layers that compose at runtime. The Next.js side holds the visual library: React components organized into named groups, each described by a `layoutId`, a human-readable name, a one-sentence description, and a Zod schema. The FastAPI backend fetches this schema at generation time through the export service, serializes it as a numbered plain-text list, and sends it to the LLM alongside the slide outline. The LLM returns a list of integer indices — one per slide — selecting from that menu based on content-type rules baked into the system prompt. Color theming runs entirely separately, generating an OKLCH-based palette with guaranteed WCAG contrast ratios that the chosen React components apply through CSS custom properties. The complete layout catalogue and its group-to-template wiring is maintained in `servers/nextjs/app/presentation-templates/index.tsx`, which serves as the single source of truth for what layouts exist and how they are grouped.

Sources: [servers/fastapi/utils/llm_calls/generate_presentation_structure.py:16-95](servers/fastapi/utils/llm_calls/generate_presentation_structure.py)
