typograph-editor-documentation
    Preparing search index...

    Typograph Editor — UI Customisation API

    This document describes how to hide or disable parts of the editor UI. It is intended for developers who embed the Typograph editor and want to tailor its interface to their application.


    // The API object is available as a global after the editor bundle loads.
    const ui = window.gEditorUI;

    Hide — the element is removed from view.
    Disable — the element remains visible but is greyed out and cannot be interacted with.

    By default, hiding a top-level section removes it from the layout entirely (display: none) so no empty space is left behind. This is called collapse.

    For fields inside a section — for example, hiding just the X-position field inside the size panel — the default is keepSpace (visibility: hidden), which makes the field invisible while keeping its space so sibling fields stay aligned.

    You can override the default for any key:

    ui.hide('parameterbar.position_size.position');                   // keepSpace (default)
    ui.hide('parameterbar.position_size.position', { collapse: true }); // collapse instead
    ui.hide('parameterbar.shadow'); // collapse (default)
    ui.hide('parameterbar.shadow', { collapse: false }); // keepSpace instead

    Hide a UI element.

    ui.hide(key: string, options?: { collapse?: boolean }): EditorUI
    

    Show a previously hidden element.

    ui.show(key: string): EditorUI
    

    Make a UI element visible but non-interactive (greyed out).

    ui.disable(key: string): EditorUI
    

    Re-enable a previously disabled element.

    ui.enable(key: string): EditorUI
    

    Whether a key is currently hidden or disabled through this API.

    ui.is_hidden(key: string): boolean
    ui.is_disabled(key: string): boolean

    They answer for exactly the key that was passed to hide() / disable(). The parts inside a hidden section disappear with it on screen — hiding parameterbar.position_size takes its X field along — but is_hidden('parameterbar.position_size.x') stays false unless that key was hidden itself.

    Apply multiple changes with a single UI update at the end. Always use batch() when configuring more than one item at startup.

    ui.batch(fn: () => void): void
    

    hide(), show(), disable() and enable() return this and can be chained.

    A key such as toolbar.select or contentbar.images names a part of the editor for this API. It is not an id or a class, and it did not change when the editor's ids and classes took the typograph- prefix in 20261007 (TYP-805). Code that hides or disables parts of the editor keeps working as it is. If your page also styles or looks up the editor's elements by id or class, see Renamed ids and classes for the old and new names.


    Keep some of the document's swatches out of the swatch palettes — colours the layouts use but the user should not pick, for example.

    ui.hide_swatches(uuids: string[]): EditorUI
    ui.show_swatches(uuids: string[]): EditorUI
    ui.get_hidden_swatches(): string[] // sorted
    ui.hide_swatches(['Color/Brand-Secret', 'PAPER']);
    

    A hidden swatch is not listed in any picker: the swatch panel behind every colour chip (fill, stroke, text colour, page colour, shadow, the toolbar) and the stop colours of the gradient and swatch dialogs. It takes effect the next time a palette opens.

    Hiding is not removing. The swatch stays in the document. An element that already uses it keeps it, draws and prints with it, and its colour chip still shows that colour — only the offer to pick it is gone. It is a setting of the editor session, not of the document: nothing is saved, so set it again when the editor starts. Unknown uuids are accepted and simply match nothing, so the call can come before the document that has those swatches is opened.

    The uuids are the ones in the document's swatch list (swatch.get_uuid()), for example 'BLACK', 'PAPER' or a converted document's 'Color/Cyan'.


    Provide a list of keywords that the editor user can insert into rich text at the cursor position.

    ui.set_keywords(keywords: string[]): EditorUI
    

    Keywords are sorted alphabetically and stored internally. Call with an empty array to clear all keywords.

    ui.get_keywords(): string[]   // the current list, sorted
    

    When at least one keyword is set, pressing ⌘K (Ctrl+K on Windows and Linux) while editing rich text opens a floating keyword popup near the cursor. The popup contains a search field that filters the list as the user types. Clicking a keyword inserts it at the cursor; the popup stays open so multiple keywords can be inserted in a row (press Esc or click outside to close it). When no keywords are set, ⌘K does nothing.

    ui.set_keywords(['city', 'company', 'date', 'first_name', 'last_name']);
    

    The call can be made at any time — before or after the editor is loaded, and can be updated at runtime (for example, when the user switches context and a different keyword set applies).


    Controls how spreads are serialised when the user saves or exports. Two independent settings are provided — one for the Save command, one for the Save As / Export command:

    ui.set_save_mode(mode: 'spreads' | 'single_pages' | 'single_pages_keep_together'): EditorUI
    ui.get_save_mode(): 'spreads' | 'single_pages' | 'single_pages_keep_together'

    ui.set_export_mode(mode: 'spreads' | 'single_pages' | 'single_pages_keep_together'): EditorUI
    ui.get_export_mode(): 'spreads' | 'single_pages' | 'single_pages_keep_together'
    Mode Behaviour
    'spreads' (default) Spreads are saved exactly as authored.
    'single_pages' Every spread is split into single pages in the output.
    'single_pages_keep_together' Spreads are split into single pages except those with "Keep pages together" on (islands) — those stay as multi-page spreads.

    Both settings default to 'spreads', so the default behaviour is unchanged. The split happens only at serialisation time — the live document, its on-screen spreads, and the undo history are not modified.

    // Save keeps spreads; Export splits them into single pages
    ui.set_save_mode('spreads');
    ui.set_export_mode('single_pages');

    // Split into single pages but keep "Keep pages together" spreads intact
    ui.set_export_mode('single_pages_keep_together');

    The call can be made at any time and updated at runtime.


    ui.set_theme(theme: 'light' | 'dark' | 'system'): EditorUI
    ui.get_theme(): 'light' | 'dark' | 'system'
    Theme Behaviour
    'light' (default) The chrome as it has always looked.
    'dark' The dark palette: the same tokens with dark values.
    'system' Follows the OS (prefers-color-scheme) and keeps following it when it changes.

    Only the chrome changes — menus, toolbar, drawers, dialogs, the stage and the rulers. The document and its colours never do, and previews of document content (images, clips) keep a transparency pattern so dark and light artwork both stay visible.

    The switch is the attribute data-typograph-theme="dark" on <html>. A host that knows the theme before the editor loads can set it in its markup instead, which avoids a light flash while the page loads; get_theme() picks it up.

    <html data-typograph-theme="dark">
    

    A host that never calls set_theme() and never sets the attribute gets the light chrome.


    ui.get_locale(): 'en' | 'nl'
    

    The language the editor shows its own texts and dates in. The host chooses it before the editor loads, with the Editor's locale option or data-typograph-locale on <html>; without either it is 'en'. See Embedding, "The language".


    Three modes: Editing, which is the editor as it has always been, Commenting, in which the canvas is inert and the user leaves anchored comments instead of changing the design, and Viewing, in which the document is read-only and there is no comment surface at all.

    ui.set_available_modes(
    modes: Array<'editing' | 'commenting' | 'viewing'>,
    options?: { render?: boolean },
    ): EditorUI
    ui.get_available_modes(): Array<'editing' | 'commenting' | 'viewing'>

    ui.set_mode(mode: 'editing' | 'commenting' | 'viewing'): EditorUI
    ui.get_mode(): 'editing' | 'commenting' | 'viewing'

    ui.get_editing_held(): boolean
    ui.get_editing_held_reason(): 'others' | 'view_only' | 'room_full'
    ui.get_room_seats(): number | null

    get_editing_held() is true while editing is withheld in a shared template: another window holds the pen — someone else's or the user's own — or this window is still loading the latest version (see "One person edits at a time" in Collaboration.md; collaboration.editing() says which). set_mode('editing') and set_mode('previewing') are refused meanwhile, whatever the host offered. Modes offered during a hold put the window in commenting when it is among them, viewing otherwise, and it returns to editing when the hold lifts. get_editing_held_reason() says why: 'others' for the pen and a version still loading, 'view_only' for a window whose collaboration token only lets the person view while editing together, and 'room_full' for a window that waits for a seat because the room seats as many people as it may (TYP-1605); the mode switcher words each differently. A view-only window in a full room keeps 'view_only': a seat would not let the person edit either. get_room_seats() is how many people the full room seats while the reason is 'room_full', null otherwise or when collab-service did not say.

    The default is ['editing'], which draws no mode switcher and changes nothing. A host that never calls set_available_modes gets exactly today's editor.

    What the host offers What the user sees
    ['editing'] (default) No switcher. Today's editor.
    ['editing', 'commenting'] A two-button switcher floating over the canvas, above the zoom control.
    ['commenting'] A locked label reading "Commenting" in the same place, with a tooltip explaining the access.
    ['viewing'] A locked label reading "View only". Inert canvas, no authoring bars, no comment rail.

    Pass { render: false } when the host draws its own control — it then owns the chrome and calls set_mode() itself, while the widget still applies the mode.

    ui.renders_mode_switcher(): boolean   // false after set_available_modes(…, { render: false })
    

    portal-ui does this: mode is the same question as sharing, and it belongs in the bar that answers it. With render left alone the widget draws the control itself, floating over the bottom-right of the canvas above the zoom readout.

    Give a commenter ['commenting'] alone rather than disabling the Editing button: the locked label is what tells them on screen why the tools are gone, and a silently inert toolbar reads as a bug.

    set_mode is refused for a mode the host has not offered. If the current mode is dropped from the available list, the first available mode is adopted.

    'previewing' is the one mode a host does not offer. It is the editor's own View ▸ Preview as viewer: available wherever Editing is, it puts the canvas under the viewer's rules so a designer can check the permissions they authored, and leaving it undoes whatever was changed meanwhile. set_mode('previewing') enters it from host code; typograph_mode_changed reports it like any other mode. See EditPermissions.md.

    Anything other than Editing calls typograph.set_interactive(false) and hides the authoring controls: the toolbar and content-bar keys documented below, plus the right-hand parameter bar and the page rail's add-page tile, which rebuild themselves and are therefore hidden by container.

    Two authoring routes need more than a muted canvas, because neither needs a selection:

    • The menu bar. Page, Arrange and Insert are hidden whole; New / Open / Save / Save as Template / Save as Clip, Cut / Paste / Delete / Step and Repeat, and the Convert to… items go with them. Hiding an item disables its keyboard shortcut too. Copy, Find and Replace, View, Save As and Export stay — the last two are downloads, which view access already grants. A menu the host hid itself through MenuAPI stays hidden when Editing returns.
    • The page rail's thumbnails, which are draggable and reorder the document when dropped. They are made undraggable for as long as the mode lasts, and the rail's own rebuilds are watched so it stays that way.

    It is not a security boundary — it is a user interface. The host still enforces permission on its own server.

    Fires whenever the rail opens, closes, or its thread count changes — enough for a host to keep its own Comments button in step.

    document.addEventListener('typograph_comments_toggled', (e) => {
    e.detail.open; // is the rail on screen
    e.detail.count; // threads it would list, under the current filter
    e.detail.available; // false when there is no comment source, or the mode is 'viewing'
    });

    Fires when the pin toggle flips, for a host drawing its own show-pins control.

    document.addEventListener('typograph_comment_pins_changed', (e) => {
    e.detail.visible; // whether pins are on screen in the current mode
    });
    document.addEventListener('typograph_mode_changed', (e) => {
    console.log(e.detail.mode); // 'editing' | 'commenting' | 'viewing'
    });

    Fires when the user applies a template from the Templates drawer, after it is applied (so after its undo step). Choosing a template replaces the shown spread with it. A template whose first spread's own page name (inside the template document, not the host entry's name) contains "Grid" adds its content to the spread instead; mode says which happened.

    document.addEventListener('typograph_template_applied', (e) => {
    e.detail.container_index; // the spread it was applied to
    e.detail.mode; // 'replace' | 'grid'
    e.detail.template.index; // position in the list the host's `templates` provider returned
    e.detail.template.name; // that entry's `name`, or null
    e.detail.template.id; // that entry's `id`, or null
    e.detail.template.url; // the url the template was loaded from
    });

    index counts the host's own list, so an entry the editor dropped (no usable url) does not shift the others. id is passed through when the host's entry carries one: a templates entry may be {name, url, id}.


    The editor renders comments and reports what the user did. It performs no requests of its own — it holds no URL and no access token, and the host supplies every read and write. That keeps the bundle free of any one backend, so an embedder can wire it to their own.

    ui.set_comment_source(source: comment_source): EditorUI
    ui.set_comments_open(open: boolean): EditorUI
    ui.get_comments_open(): boolean
    ui.toggle_comments(): EditorUI
    ui.set_current_user(user: { id: string, name: string, avatar_url?: string }): EditorUI
    ui.refresh_comments(): EditorUI
    ui.select_comment(id: string): EditorUI

    ui.set_comment_pins_visible(visible: boolean): EditorUI
    ui.get_comment_pins_visible(): boolean
    ui.toggle_comment_pins(): EditorUI

    Nothing is drawn until set_comment_source is called: no pins, and no rail.

    The surface follows Google Drive's commenting, which is the interaction most reviewers already know:

    • A pin per thread carrying the thread starter's avatar, anchored in page millimetres so it holds its place under zoom, pan and page reorder. Replies do not change the face — they add a small count badge, and who replied is in the rail. Hovering a pin highlights its card and the other way round.
    • Pins that land within a pin's width of each other are drawn as one cluster: two faces stacked, plus a +N. Hover spreads the faces, a click opens a picker listing every thread in the group — the only way to reach the one underneath. The cluster is computed on screen positions, so zooming in dissolves it.
    • Pins are hidden while editing, and follow the rail: opening it shows them, closing it hides them again. The ◉ in the rail header is the override in between — hide the markers, keep reading the threads — and set_comment_pins_visible() is the same switch for a host. Commenting always draws the pins and Viewing never does, so get_comment_pins_visible() answers for the live mode rather than for the raw toggle.
    • Pins, clusters and pickers are clipped to the artwork area — the canvas minus the rulers, the page bar, the content panel, the scrollbars and the rail itself. A marker can never be drawn on the chrome, at any zoom or scroll position, and never takes a click meant for a control.
    • A comment on the current page that falls outside that area is shown as an edge indicator: a small arrow against the canvas edge, pointing at where the comment went and carrying the starter's face. Clicking it pans the view until the comment sits in the middle and opens the thread. Comments landing on the same stretch of edge share one arrow with a +N. Pins on another page stay hidden, as before — the indicator is only for this page.
    • Clicking a pin or a card navigates to the page the comment sits on, and opens the thread in the rail.
    • A 320px rail of cards, folded until it is asked for: the pins already say a page carries feedback, and the rail costs canvas. It unfolds on set_comments_open(true), on clicking a pin or an edge indicator, on starting a comment, and on a deep link; it folds again on its tab, on the ✕ in its header, with Escape, or on set_comments_open(false). There is no third state: the ✕ and the tab do the same thing.
    • The rail is a second tools drawer on the same edge: the same little grey tab with the arrow that flips over, the same 0.2s slide, and the canvas narrowed by the rail's width while it is open (set_offset_right + resize_main_canvas, the pair the parameter bar uses). It takes its top from the parameter bar's own, so a host that moves the editor chrome moves the rail with it.
    • Two tabs, stacked: the tools tab keeps the top of the edge, the comments tab sits directly under it, and both stand against whichever drawer is open — so they travel with it and never land on top of each other. A tab is its mark and nothing else — sliders for the tools, a speech bubble for the comments — because two grey tabs on one edge otherwise say nothing about which is which, and the arrow they used to carry only announced a slide you can watch happen. The tab of the drawer that is open is the lit one. The editor puts the tools mark there itself rather than expecting it in the markup: a host brings its own editor shell, and portal-ui's predates this. Each tab shows only when its mode is on offer: no editing in set_available_modes() means no tools tab, no commenting (or no comment source) means no comments tab.
    • One drawer at a time. The edge holds 320px or 250px, never both: opening one folds the other, and the canvas gets the difference back. A folded drawer costs nothing but its tab.
    • A tab also carries the mode with it, because each drawer belongs to one: pressing the comments tab from Editing switches to Commenting and opens the rail; pressing the tools tab from Commenting switches back to Editing and opens the parameter bar. Where the other mode is not on offer the tab is simply a fold toggle. The thread count lives in the rail header only — a host's own comment button already carries it.
    • Inside it, the open thread expands and the rest collapse to two lines and a reply count. Open / All in the header filters resolved threads.
    • Clicking the canvas drops the pin immediately and opens a composer. ⌘/Ctrl + Enter sends, Esc discards the draft and its pin, ⌘/Ctrl + Alt + M starts one in the middle of the view.
    • Resolve is the ✓ on the open card; edit and delete are behind its ⋮, and only on the reader's own comments and replies.
    • select_comment(id) given before the first list() resolves is remembered and applied when the threads arrive, so a deep link does not race the network.

    Every method is async and may reject. The widget applies the change locally first and rolls it back if the host rejects it, so a dropped pin appears under the cursor rather than a third of a second later — and disappears again if the write fails.

    interface comment_source {
    list: () => Promise<comment_thread[]>;
    create: (comment: string, position: comment_position) => Promise<comment_thread>;
    update: (id: string, comment: string) => Promise<comment_thread>;
    resolve: (id: string, resolved: boolean) => Promise<void>;
    remove: (id: string) => Promise<void>;
    create_reply: (comment_id: string, comment: string) => Promise<comment_reply>;
    update_reply: (comment_id: string, reply_id: string, comment: string) => Promise<comment_reply>;
    remove_reply: (comment_id: string, reply_id: string) => Promise<void>;
    }

    interface comment_position {
    page_uuid: string; // the Spread uuid the pin is anchored to
    x: number; // native millimetres, not screen pixels
    y: number;
    }

    interface comment_thread {
    id: string;
    user: { id: string, name: string, avatar_url?: string };
    comment: string;
    position: comment_position;
    resolved: boolean;
    created_at: string; // ISO 8601
    updated_at: string;
    replies: comment_reply[];
    }

    interface comment_reply {
    id: string;
    user: { id: string, name: string, avatar_url?: string };
    comment: string;
    created_at: string;
    updated_at: string;
    }

    There is deliberately no list_replies: list returns each thread with its replies already, and a second round trip per thread would buy nothing.

    Positions are native millimetres, so a comment placed at one zoom level lands in the same spot at another. page_uuid is the canvas Spread.uuid — stable across page insert and reorder, where an index is not. A thread whose page_uuid is an empty string is skipped rather than drawn on page one, which is how comments predating anchoring are handled.

    gEditorUI.set_current_user({ id: 'u-17', name: 'Robin de Vries' });

    gEditorUI.set_comment_source({
    list: () => fetch('/api/comments').then(r => r.json()),
    create: (comment, position) => fetch('/api/comments', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ comment, position }),
    }).then(r => r.json()),
    // …update, resolve, remove, create_reply, update_reply, remove_reply
    });

    gEditorUI.set_available_modes(['editing', 'commenting']);

    select_comment(id) brings the thread's page into view and expands its card — for landing a reader on the comment a notification named.

    Event detail
    typograph_comment_created { id }
    typograph_comment_updated { id }
    typograph_comment_resolved { id, resolved }
    typograph_comment_deleted { id }
    typograph_comment_selected { id } — id is null when the selection is cleared

    When a host sets legacy_php_fallback: false (see Embedding.md), the editor hides UI that no host provider backs. One entry stays visible if any of its capabilities is available:

    UI Key / menu item Shown when available
    Images tab typograph-contentbar-image images or manifest (incl. manifest_data)
    Clips tab typograph-contentbar-clips clips
    Templates tab typograph-contentbar-page templates
    File ▸ Save (and Cmd+S) menutitle-file_Save save_document
    File ▸ Save as template menutitle-file_Save_as_Template save_template
    File ▸ Save as clip menutitle-file_Save_as_Clip save_clip

    These always stay: Save As (local file), Open local, and everything that lives only inside the document. The mapping is CAPABILITY_UI_MAP in src/capabilityui.ts. It re-applies whenever the host registry changes, and it only ever shows again items it hid itself. A menu item you hid through MenuAPI.hideItem() stays hidden. With the fallbacks on (the default on latest), nothing is hidden.

    Parent keys target the whole group. Child keys (indented) target individual items within that group.

    Key What it controls
    toolbar.select Selection tool
    toolbar.path Path / pen tool
    toolbar.grab Pan / grab tool
    toolbar.undo_redo Undo and Redo buttons (both together)
      toolbar.undo_redo.undo Undo button only
      toolbar.undo_redo.redo Redo button only
    toolbar.fill_color Fill colour swatch
    toolbar.stroke Stroke palette — icon trigger; opens a popup
      toolbar.stroke.stroke_size Stroke width input
      toolbar.stroke.stroke_pattern Stroke dash pattern selector
      toolbar.stroke.line_cap Line cap buttons
      toolbar.stroke.line_join Line join buttons
    toolbar.opacity_blend Opacity + blend palette — icon trigger; opens a popup
      toolbar.opacity_blend.opacity Opacity control
      toolbar.opacity_blend.blend_mode Blend mode selector
    toolbar.text_format All text formatting controls
      toolbar.font_face Font family selector
        toolbar.font_face.default_font Default font (Source Sans Pro) entry in the font picker
        toolbar.font_face.manage_fonts "Manage fonts…" item at the top of the font picker
      toolbar.font_style Font style selector
      toolbar.font_size Font size input
    toolbar.text_align Horizontal justification (icon popupmenu)
    toolbar.text_valign Vertical alignment (icon popupmenu)
    toolbar.decoration Underline / strikethrough palette — icon trigger; opens a popup
      toolbar.decoration.underline Underline switch + weight / offset / colour
      toolbar.decoration.strikethrough Strikethrough switch + weight / offset / colour
    toolbar.spacing Spacing palette — icon trigger; opens a popup
      toolbar.spacing.line_spacing Line spacing input
      toolbar.spacing.letter_spacing Letter spacing input
    toolbar.columns Columns palette — icon trigger; opens a popup
      toolbar.columns.columns_count Column count icon + input
      toolbar.columns.columns_gutter Gutter icon + input
      toolbar.columns.columns_balanced Balanced / unbalanced toggle buttons
    toolbar.drop_cap Drop-cap palette — icon trigger; opens a popup
      toolbar.drop_cap.drop_cap_lines Drop cap label + lines input
      toolbar.drop_cap.drop_cap_chars Chars label + characters input
      toolbar.drop_cap.drop_cap_size Size label + font size input
    toolbar.hyphenation Hyphenation palette — icon trigger; opens a popup
      toolbar.hyphenation.hyphenation_toggle Hyphenate label + on/off switch
      toolbar.hyphenation.hyphenation_before Before label + min-chars-before-break input
      toolbar.hyphenation.hyphenation_after After label + min-chars-after-break input
      toolbar.hyphenation.hyphenation_limit Limit label + max-consecutive-hyphenated-lines input (0 = unlimited)
    toolbar.spellcheck Spell-check palette — icon trigger; opens a popup
      toolbar.spellcheck.spellcheck_toggle Spell check label + on/off switch
    toolbar.image_controls Image scale, horizontal and vertical alignment
    toolbar.table Table rows and columns palette (text editing, caret in a table)
    toolbar.table_stroke Table cell strokes palette (a structural table selection)
    toolbar.cell_fill Table cell background (a structural table selection)
    toolbar.autofit Fit text on/off button: the viewer only, on a frame with a Fit text rule

    Popup palettes: several groups (toolbar.stroke, toolbar.opacity_blend, toolbar.decoration, toolbar.spacing, toolbar.columns, toolbar.drop_cap, toolbar.hyphenation, toolbar.spellcheck) render as an icon-only trigger that opens a flyout panel. Hiding/disabling the palette key affects the trigger; the indented …<control> keys target individual controls inside the popup and are applied each time the popup opens.

    Text-edit vs frame toolbar: when editing text (double-click), the toolbar shows stroke + opacity/blend palettes, the justification popupmenu, and the decoration / spacing palettes. When a text frame is selected (not editing), it shows the vertical-alignment popupmenu and the columns, drop-cap, hyphenation and spell-check palettes. toolbar.text_align and toolbar.text_valign are now single icon popupmenus (no per-direction sub-keys).

    Key Tab
    contentbar.pages Pages / templates
    contentbar.text Text snippets
    contentbar.shapes Shapes
    contentbar.images Images
    contentbar.clips Clips
    contentbar.layers Layers

    Shown when the user clicks the canvas background.

    Key Section
    parameterbar.page.name Page name
    parameterbar.page.size Page width and height (both together)
      parameterbar.page.width Page width field only
      parameterbar.page.height Page height field only
    parameterbar.page.type Page type selector
    parameterbar.page.fill_color Page background colour
    parameterbar.page.bleed Entire bleed section
    parameterbar.page.locked Page lock chip (padlock plus Locked / Unlocked)

    Individual bleed fields — default to keepSpace

    Key Field
    parameterbar.page.bleed.top Bleed top
    parameterbar.page.bleed.right Bleed right
    parameterbar.page.bleed.bottom Bleed bottom
    parameterbar.page.bleed.left Bleed left
    parameterbar.page.bleed.link Link / unlink all values button

    Sections

    Key Section
    parameterbar.position_size Entire name / position / size panel
    parameterbar.rotate Rotate & Shear (rotation, shear, turns about)
    parameterbar.corners Corner radius
    parameterbar.shadow Shadow
    parameterbar.autofit Fit text (text frames only)
      parameterbar.autofit.enabled Fit text on/off switch, in its header
    parameterbar.textwrap Text-wrap section (entire panel)
    parameterbar.role Role (in the viewer)
    parameterbar.details Details — UUID and, for an image, the effective resolution. Folded by default; closes every bar

    Fields within the size / position section — default to keepSpace

    Key Field(s)
    parameterbar.position_size.name Element name input — now the title card at the top of the bar, above parameterbar.position_size; hiding parameterbar.position_size no longer hides the name
    parameterbar.locked Lock padlock (closed = locked) — sits at the right edge of the Position & size header (not its own section since TYP-766 Task 3); hiding parameterbar.position_size hides it too
    parameterbar.position_size.position X and Y position
    parameterbar.position_size.size Width and Height
    parameterbar.position_size.reference 9-point reference selector
    parameterbar.position_size.page_anchor Sits against — the page edge an element follows, and the distance from it (two wrappers share this key: the edge choice and the distance field)
    parameterbar.rotation_origin Turns about — the nine box positions an element rotates about
    parameterbar.shear Shear — the shear angle field and its slider, beside the rotation angle

    Text-wrap sub-controls — default to collapse

    Shown on the obstructing element (a shape/image that text flows around). The offset, side and inverse controls only appear once a wrap mode other than none is chosen.

    Key Control
    parameterbar.textwrap.mode The four wrap-mode icons (none / bounding box / jump / shape)
    parameterbar.textwrap.offset.top Standoff offset input, top
    parameterbar.textwrap.offset.left Standoff offset input, left
    parameterbar.textwrap.offset.bottom Standoff offset input, bottom
    parameterbar.textwrap.offset.right Standoff offset input, right
    parameterbar.textwrap.side Side selector (both / left / right / largest area)
    parameterbar.textwrap.inverse Inverse toggle (flow text inside the shape)

    With more than one element selected the bar opens with a title card — elements, Several selected — and the Position & size block for the selection's bounding box, with the lock padlock in its header. Below it come the sections every selected element has, in the same order as the single bar: Rotate & Shear, Corners, Shadow, Fit text, Text wrap, Role. A text frame and an image share no Fit text; anything grouped with a group shares no Shadow. The UUID-bearing Details section is left out.

    The sections use the same keys as the element panel, so one hide covers both bars:

    Key Section
    parameterbar.multi The whole multi bar — title card, Position & size block and every section below it (multi_wrap wraps #parmetersbarcontent too, not just the title card)
    parameterbar.locked Lock padlock in its header — applies to every selected element
    parameterbar.rotate Rotate & Shear (rotation, shear, turns about)
    parameterbar.corners Corner radius (rounded rectangles and unclipped images only)
    parameterbar.shadow Shadow
    parameterbar.autofit Fit text (text frames only)
    parameterbar.textwrap Text wrap
    parameterbar.role Role

    A control whose value differs across the selection shows as mixed: a switch reads as off and is drawn dashed, a number field is empty with the placeholder —. Setting it writes the value to every selected element as one undo step. Fit text edits its rule list only when every frame has the same rules; otherwise it shows the switch and Rules differ per frame. The shadow colour chip edits every selected element too; when their shadow colours differ it shows empty in a dashed well. A locked element in the selection is never written to; after the header's padlock locks the selection, the sections below it are greyed.

    The toolbar keeps showing the controls of the top-most selected element, but its fill colour, stroke colour (toolbar.stroke) and opacity (toolbar.opacity_blend.opacity) now write to every selected element — a colour pick or an opacity drag is one undo step. The colour picker these open highlights no swatch when the selection's colours differ, and the opacity field is empty with — when their opacities differ. Text colours of a text selection and table-cell fills still apply to the one frame being edited.


    ui.batch(() => {
    ui.hide('toolbar.path');
    ui.hide('toolbar.grab');
    });
    ui.batch(() => {
    ui.hide('contentbar.pages');
    ui.hide('contentbar.text');
    ui.hide('contentbar.shapes');
    ui.hide('contentbar.clips');
    });
    ui.batch(() => {
    ui.hide('parameterbar.shadow');
    ui.hide('parameterbar.rotate');
    ui.hide('parameterbar.corners');
    });
    ui.hide('parameterbar.position_size.name');
    // X, Y, Width, Height fields remain visible and aligned
    ui.disable('toolbar.blend_mode');
    ui.disable('parameterbar.rotate');
    document.addEventListener('typograph_setup_ready', () => {
    ui.batch(() => {
    ui.hide('toolbar.path');
    ui.hide('toolbar.grab');
    ui.hide('toolbar.blend_mode');
    ui.hide('toolbar.text_baseline');

    ui.hide('contentbar.pages');
    ui.hide('contentbar.text');
    ui.hide('contentbar.shapes');
    ui.hide('contentbar.clips');

    ui.hide('parameterbar.shadow');
    ui.hide('parameterbar.rotate');
    ui.hide('parameterbar.position_size.name');
    });
    });
    // Hide initially, reveal later based on application state
    ui.hide('toolbar.drop_cap');

    // ... later ...
    ui.show('toolbar.drop_cap');
    ui.set_keywords(['city', 'company', 'date', 'first_name', 'last_name']);
    // Press ⌘K while editing text to open the keyword popup
    // Different keyword sets for different document types
    function on_document_type_changed(type: string) {
    if (type === 'invoice')
    ui.set_keywords(['company', 'invoice_date', 'invoice_number', 'total']);
    else if (type === 'letter')
    ui.set_keywords(['city', 'date', 'first_name', 'last_name', 'salutation']);
    else
    ui.set_keywords([]); // disable keyword popup
    }

    The size panel carries a third rule beside position and size: which page edge an element sits against, and how far from it.

    Choice Means
    No page edge The element's rect is absolute. It stays where it is when its page moves. This is every element in every document written before the feature.
    Inside (spine) Measured from the edge that faces the spine.
    Outside Measured from the edge that faces away from the spine.

    Inside and outside, never left and right: the whole point is that the element keeps its place when its page moves from a right-hand to a left-hand position, and a rule stated as left cannot do that. Which physical edge it means is decided by the page's number — page 1 is a recto — so it is answered in the canvas, by the same counting the page bar uses.

    ⚠️ Choosing an edge does not move the element. The distance is measured from where it already sits, so the rule reads "stay this far from that edge". Typing a distance is the instruction that moves it.

    ⚠️ An anchored element's X is the rule's output. It is re-resolved on every structural change — a page added, deleted, reordered, a spread split or merged, facing pages toggled. Dragging or resizing the element updates its distance to match, so a drag means "and this is the new distance"; the X field reports the resolved position either way.

    The distance field counts up and down while you drag or resize, not only when you let go — so what the rule is about to remember is visible as it changes. A resize moves it too, because the distance is measured to the element's nearer edge and a resize moves that edge without moving the element's origin.

    ⚠️ A group holds the rule for its children. Anchor the group, not the items inside it: a child's coordinates are already absolute in spread space, so a rule on both would move it twice.


    The Rotate & Shear panel's Shear field slants an element: InDesign's Shear X Angle, with its sign — positive leans the top to the right, like italic type. Type a value, or open the slider beside it, which runs from −60° to +60° as InDesign's does. A typed value is one undo step, and so is a whole slider drag. With several elements selected it shears every unlocked one.

    The element keeps its box: its width, height and X/Y do not change, only how it is drawn. Text, fills and pictures in a sheared frame slant with it; the text is laid out as before.

    ⚠️ The field shows the angle the element has, even beyond ±60°: a document converted from InDesign can carry a steeper one, which the renderer draws up to ±89.9°. Only a typed value is held to the field's range.

    Print agrees with the screen: the angle is saved with the element, and the PDF applies it the same way.


    The Rotate panel carries the nine box positions on the same control the size panel uses for the X/Y reference point. Pick one and the element turns about that corner, edge midpoint or centre instead of its middle.

    ⚠️ It is a position, not a coordinate. Rotate about the top-left stays true when the box is resized — the renderer works the point out from the box every time it draws, so there is nothing stored to fall out of step. The centre is the default, and an element that has never been given an origin turns about its middle exactly as before.

    ⚠️ Print agrees with the screen without the publisher knowing about any of this: the canvas writes the resulting point into the document beside the choice, and that is what the PDF reads.