Write the panel grid, images, speech bubbles, captions and sound effects in YAML or JSON and get a manga page on the spot. Images can be given by URL or uploaded
You can write the script by hand, but you can also hand the spec below to an AI together with the plot, characters and lines in plain words, and it returns YAML that follows this spec. Paste it into the box above and it renders.
You write manga scripts in a YAML format called "Manga Spec v1".
Output only YAML in this format (no explanation, no code fences).
■ Overall shape (top-level keys)
version: 1
page: # the whole page (px)
width: 1200
height: 1700
background: "#ffffff"
margin: 48 # outer margin
gutter: 24 # gap between panels
order: ltr # reading order of panels. ltr = left to right, rtl = right to left (Japanese manga; the default)
images: # optional names for images. Value is a URL or the name of an uploaded file
hero: https://example.com/a.jpg
layout: # panel grid. rows lists, top to bottom, either "number of panels in the row" or "width ratios of its panels"
rows: [2, 1, [1, 2]] # row 1 has 2 panels, row 2 has 1, row 3 has 2 panels with widths 1:2 (ratios follow reading order)
rowHeights: [1, 1.4, 1] # height ratios of the rows (optional; equal when omitted)
panels: # array of panels. They fill the layout cells in reading order
- image: hero # image: name / URL / uploaded file name / {src: …, fit: …}
bubbles: # speech bubbles
- text: "Morning!\nNice weather today"
x: 70 # centre position (% of the panel)
y: 25
tail: [40, 70] # tip of the tail (% of the panel); point it at the speaker's mouth
captions: # narration (white text on a black box)
- text: The next morning
pos: top-left
sfx: # sound effects (large outlined text)
- text: BOOM
x: 60
y: 70
rotate: -12
■ Panel (each element of panels)
image image. A string (URL / name in images / uploaded file name) or the object below
src the image (required)
fit cover (fill the panel; default) / contain (fit whole image) / fill (stretch) / none (natural size)
scale magnification (default 1). cover with 1.3 gives a closer crop
x, y focal point to keep (%, default 50). With cover, x: 0 keeps the left edge, 100 the right edge
offsetX, offsetY nudge in px
flipX, flipY true to mirror horizontally / vertically
opacity 0-1
filter CSS filter (grayscale(1) / sepia(0.6) / contrast(1.3) …)
rotate degrees
background panel background colour (default #ffffff)
border frame. {width: 5, color: "#000"}, a number (width), or false (no frame)
radius corner radius (px)
x, y, w, h when all four are given, the panel ignores the grid and is placed freely in % of the page's inner area
z stacking order (larger is in front)
bubbles / captions / sfx arrays, see below
■ Speech bubble (each element of bubbles). A bare string is taken as text
text dialogue (required). Line breaks with \n or a YAML multi-line string
x, y centre position (% of the panel, default 50)
w, h size (% of the panel). Omitted: sized automatically from the text
shape round (ellipse; default) / rect (rounded box) / burst (jagged, shouting) / thought (bumpy, inner voice) / cloud / none (no outline)
tail [x, y] (tip in % of the panel) / a direction name (bottom-left, top, right …) / none (default)
thought bubbles automatically get a trail of small circles instead
vertical true (vertical text) / false (horizontal) / auto (default: vertical when the text contains Japanese)
font {size: 28, family: "…", weight: 700, style: normal, color: "#000"}
fill, stroke, strokeWidth fill colour, outline colour and width (defaults white / black / 3)
padding space between text and outline (px, default 14)
lineHeight line spacing (ratio, default 1.35)
align left / center / right
maxWidth, maxHeight upper bound for automatic sizing (% of the panel, defaults 46 / 60)
rotate degrees
opacity 0-1
autoFit true (default) shrinks the text to fit inside w / h
keepInside true (default) pushes a bubble that would stick out back inside the panel
■ Caption (each element of captions)
text (required), pos (top-left / top / top-right / left / center / right / bottom-left / bottom / bottom-right),
x, y (top-left corner in %; overrides pos), w (%), font, color, fill (default #000), stroke, strokeWidth, padding, lineHeight, align, maxWidth, rotate, opacity
■ Sound effect (each element of sfx)
text (required), x, y (centre in %, default 50), size (px, default 90), rotate (default -12), color (default black),
stroke (outline colour, default white), strokeWidth (default 10), font (family / weight / style), letterSpacing, opacity, vertical
■ Changing defaults in one place (optional)
defaults:
panel: {border: {width: 6}, background: "#fff"}
bubble: {font: {size: 30}, vertical: false, tail: bottom-left}
caption: {fill: "#000", font: {size: 24, color: "#fff"}}
sfx: {size: 100, color: "#d00"}
image: {fit: cover, filter: grayscale(1)}
■ Several pages (optional)
pages:
- layout: {rows: [2, 1]}
panels: [ … ]
- layout: {rows: [1, 1]}
panels: [ … ]
■ Rules
- The number of panels must not exceed the number of layout cells (freely placed panels do not count).
- Percentages normally run 0-100. Bubble x, y are the centre; caption x, y are the top-left corner.
- Keep each bubble to about 30 characters. Split long lines into several bubbles.
- Point the tail at the speaker's mouth. If the speaker is visible in the image, match that position.
- A panel without an image may use a background colour plus captions or sfx to describe the situation.
- Colours are #rrggbb or colour names. Unknown keys are ignored (mind the spelling).
■ Example (4-panel strip, English, left to right)
version: 1
page: {width: 800, height: 2000, margin: 40, gutter: 20, order: ltr}
layout: {rows: [1, 1, 1, 1]}
panels:
- background: "#e8f0fe"
captions: [{text: "1", pos: top-left}]
bubbles: [{text: "Let's make a comic today", x: 40, y: 40, tail: [70, 85]}]
- background: "#fff3e0"
captions: [{text: "2", pos: top-left}]
bubbles: [{text: "But I can't draw…", x: 60, y: 45, shape: thought, tail: [25, 85]}]
- background: "#fce4ec"
captions: [{text: "3", pos: top-left}]
bubbles: [{text: "Photos plus\nspeech bubbles\nwould do!", x: 40, y: 40, shape: burst, tail: [70, 85]}]
sfx: [{text: "DING", x: 78, y: 30, size: 60}]
- background: "#e8f5e9"
captions: [{text: "4", pos: top-left}]
bubbles: [{text: "Done.", x: 50, y: 45, shape: rect}]
Now write the following in this YAML format.
Content:
| Where | Key | Meaning |
|---|---|---|
| top level | version | Spec version. Currently 1. |
| page | width / height / background / margin / gutter / order | Page size (200-6000 px), background colour, outer margin, gap between panels and reading order (rtl right to left, ltr left to right). Defaults: 1200×1700, margin 48, gutter 24, rtl. |
| images | any name: URL | Names images. The value is a URL, a data: URL or the file name of an uploaded image. |
| layout | rows / rowHeights | Panel grid. Three notations: rows: [2, 1] (panels per row), rows: [[1, 2]] (width ratios in reading order) and "2x3" (columns×rows). Omitted: one column with as many rows as panels (good for strips). |
| panels[] | image / background / border / radius | Panel image, background colour, frame ({width, color}, a number or false) and corner radius. |
| panels[] | x / y / w / h / z | All four together place the panel freely in % of the page's inner area. z is the stacking order. |
| image | src / fit / scale / x / y / offsetX / offsetY / flipX / flipY / opacity / filter / rotate | How the image is shown. fit is cover / contain / fill / none. x y are the focal point (%). filter is a CSS filter string. |
| bubbles[] | text / x / y / w / h / shape / tail / vertical | Dialogue, centre position (%), size (%, automatic when omitted), shape (round / rect / burst / thought / cloud / none), tail ([x, y], a direction name or none) and vertical text (true / false / auto). |
| bubbles[] | font / fill / stroke / strokeWidth / padding / lineHeight / align / maxWidth / maxHeight / rotate / opacity / autoFit / keepInside | Fine control of the look. font is {size, family, weight, style, color}. |
| captions[] | text / pos / x / y / w / font / fill / stroke / strokeWidth / padding / align / rotate | Narration box. pos is one of nine places such as top-left. x y are the top-left corner in %. |
| sfx[] | text / x / y / size / rotate / color / stroke / strokeWidth / font / letterSpacing / vertical | Sound effect: large text with an outline (stroke). |
| defaults | panel / image / bubble / caption / sfx | Changes the defaults of each element in one place. Values on the element itself win. |
| pages[] | page / layout / panels | Several pages. Top-level page and defaults apply to all of them. |
rows: [2, 1, 2] gives five panels in three rows (2, 1 and 2), and the elements of panels fill the cells in reading order (right to left and top to bottom by default; set order: ltr for Western comics). Write a row as [1, 2] to change the widths, and use rowHeights for the row heights. Panels beyond the number of cells are not drawn and are reported. A panel that should ignore the grid gets all four of x / y / w / h in % and is placed freely; z decides what is in front.
The default fit: cover fills the panel with the image and crops the rest. x and y (%) decide which part is kept: x: 0 keeps the left edge, x: 100 the right edge. scale: 1.5 zooms in; fit: contain shows the whole image. Reusing one image in several panels with different x / y / scale gives you a wide shot and a close-up from a single photo.
Write tail: [40, 70] with the panel coordinates (%) of the speaker's mouth and the tail stretches towards it. If only the direction matters, a name such as tail: bottom-left works too. An inner voice uses shape: thought, which turns the tail into a trail of small circles. Shouting suits burst, explanatory lines suit rect. Japanese text is set vertically by default; vertical: false makes it horizontal, and English text is horizontal unless you ask otherwise.
Broken YAML / JSON syntax is reported with its line. A value outside the spec is reported with its path, such as panels[1].bubbles[0].x, and an approximate line. Misspelled keys are ignored when drawing but listed as yellow warnings so you notice them. If even one value is wrong, nothing is drawn and only the report is shown, so that a half-right picture is never mistaken for a correct one.
PNG saves exactly what the preview shows (pick a scale). SVG saves bubbles and text as shapes, so you can move dialogue or change colours later in Illustrator or Inkscape. Inside the SVG, uploaded images are embedded and URL images stay as URL references. Fonts depend on the viewer's system. A multi-page script is saved as one file per page.
No. Parsing the YAML / JSON, loading images, laying out panels and speech bubbles, and exporting PNG / SVG all happen inside your browser. Neither image files nor dialogue is uploaded or stored anywhere.
Both express exactly the same document. YAML is shorter to write by hand because it needs no quotes or braces; JSON is the safer choice when a program or an AI generates the script. The input box detects the format from the first character, and one button converts between the two.
When an image comes from another site whose server does not allow CORS, the browser's security model forbids reading it back out of the canvas. It can still be shown in the preview, but not saved. Save that image to your computer, upload it on this page and refer to it by file name, and the export works.
Yes. Copy the "Spec prompt for AI" on this page, append what you want drawn, and hand it to an AI. It returns YAML that follows this spec; paste it into the input box and it renders. Values outside the spec are reported with a line number, so you can pass the message back to the AI to fix it.