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(key, options?)Hide a UI element.
ui.hide(key: string, options?: { collapse?: boolean }): EditorUI
.show(key)Show a previously hidden element.
ui.show(key: string): EditorUI
.disable(key)Make a UI element visible but non-interactive (greyed out).
ui.disable(key: string): EditorUI
.enable(key)Re-enable a previously disabled element.
ui.enable(key: string): EditorUI
.is_hidden(key) / .is_disabled(key)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.
.batch(fn)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'.
.set_keywords(keywords)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:
MenuAPI stays hidden when Editing returns.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.
typograph_comments_toggledFires 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'
});
typograph_comment_pins_changedFires 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
});
typograph_mode_changeddocument.addEventListener('typograph_mode_changed', (e) => {
console.log(e.detail.mode); // 'editing' | 'commenting' | 'viewing'
});
typograph_template_appliedFires 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:
+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.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.+N. Pins on another page stay
hidden, as before — the indicator is only for this page.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.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.editing in set_available_modes() means no
tools tab, no commenting (or no comment source) means no comments tab.Open / All in the header filters resolved threads.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_alignandtoolbar.text_valignare 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 —
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.