You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
description: 'Build your own UI for Word content controls (SDT fields): turn off the built-in chrome, react to the active control, and anchor your UI with getRect().'
4
+
---
5
+
6
+
Turn off SuperDoc's built-in chrome, listen for the active control, and anchor your own UI over it. The control wrappers and `data-sdt-*` attributes stay in the DOM, so your UI has something to attach to.
7
+
8
+
## A minimal field chip
9
+
10
+
```ts
11
+
import { SuperDoc } from'superdoc';
12
+
import { createSuperDocUI } from'superdoc/ui';
13
+
14
+
newSuperDoc({
15
+
selector: '#editor',
16
+
document: '/contract.docx',
17
+
// Turn off the built-in labels, borders, and hover/selection chrome.
18
+
modules: { contentControls: { chrome: 'none' } },
19
+
onReady: ({ superdoc }) => {
20
+
const ui =createSuperDocUI({ superdoc });
21
+
22
+
superdoc.on('content-control:active-change', ({ active }) => {
23
+
if (!active) returnchip.hide(); // `chip` is your own element
24
+
const rect =ui.contentControls.getRect({ id: active.id });
25
+
if (rect.success) chip.showAt(rect.rect, active.alias??active.tag);
26
+
});
27
+
},
28
+
});
29
+
```
30
+
31
+
The event tells you *what* is active; `getRect` tells you *where* to draw. `active` is an `SdtRef` with `id`, `tag`, `alias`, `controlType`, and `scope`.
32
+
33
+
## Pick the right surface
34
+
35
+
| Goal | API |
36
+
| --- | --- |
37
+
| Active control (enter, switch, leave) |`superdoc.on('content-control:active-change')`|
38
+
| Click inside a control |`superdoc.on('content-control:click')`|
39
+
| Full live list and active stack |`ui.contentControls.observe()` / `getSnapshot()`|
40
+
| Read one control |`ui.contentControls.get({ id })`|
41
+
| Position your UI |`ui.contentControls.getRect({ id })`|
42
+
| Hover and right-click hit-testing |`ui.viewport.entityAt()` / `contextAt()`|
43
+
| Change content, tags, or locks |`editor.doc.contentControls.*`|
44
+
45
+
`active` is the innermost control. For nested controls (an inline field inside a block clause), `activePath` carries the full stack, innermost first, so you don't also need `observe()` just to read the nesting.
46
+
47
+
## Current limits
48
+
49
+
- No built-in scroll-to-control. Read the position with `getRect()` and scroll your container.
50
+
- No geometry-change subscription. Re-read `getRect()` on scroll, resize, and the `pagination-update` / `zoomChange` events.
51
+
- No focus-by-id helper. Clicking a control in the document still drives selection.
52
+
53
+
## See also
54
+
55
+
-[Contract templates demo](https://github.com/superdoc-dev/superdoc/tree/main/demos/contract-templates) - a working field chip built on these APIs.
56
+
-[Configuration](/editor/superdoc/configuration) - the `modules.contentControls.chrome` option.
57
+
-[Document API: content controls](/document-api/features/content-controls) - read and change controls.
// activePath: SdtRef[] - full active stack, innermost first ([] when none)
257
+
});
258
+
```
259
+
260
+
`SdtRef` shape:
261
+
262
+
```ts
263
+
typeSdtRef= {
264
+
id:string;
265
+
tag?:string;
266
+
alias?:string;
267
+
controlType:string;
268
+
scope:'inline'|'block';
269
+
};
270
+
```
271
+
272
+
`active` is the deepest control (`activePath[0]`); `activePath` holds the full stack, innermost first. Controls without an `id` do not emit these events.
0 commit comments