apps/docs/content/sdk-features/visibility.mdx
The editor's visibility system determines which shapes are rendered on screen. It handles two separate concerns: culling, which hides off-screen shapes for performance, and hidden shapes, which your application hides explicitly.
Culling is a performance optimization. Shapes outside the viewport stay in the store and can still be selected, updated, or queried, but the editor sets display: none on their DOM elements. Selected shapes and the shape being edited are never culled. Return false from ShapeUtil#canCull to opt a shape out.
You can get the set of culled shape IDs with Editor#getCulledShapes:
const culledIds = editor.getCulledShapes()
See Culling for how the spatial index decides what to cull and for the full set of culling methods.
Hidden shapes are shapes your application explicitly hides using the getShapeVisibility option. Unlike culled shapes, hidden shapes are excluded from hit tests, exports, and rendering.
import { Tldraw } from 'tldraw'
import 'tldraw/tldraw.css'
export default function App() {
return (
<div style={{ position: 'fixed', inset: 0 }}>
<Tldraw
getShapeVisibility={(shape) => {
if (shape.meta.hidden) return 'hidden'
return 'inherit'
}}
/>
</div>
)
}
The function can return:
| Return value | Behavior |
|---|---|
'inherit', undefined, or null | Shape is visible unless its parent is hidden (default). |
'hidden' | Shape is always hidden. |
'visible' | Shape is always visible, even if parent is hidden. |
The same prop is available on TldrawEditor and as an Editor constructor option. The editor caches the result per shape, so keep the callback pure.
Hidden shapes are still in the store. They're just excluded from:
You can check if a shape is hidden with Editor#isShapeHidden:
if (editor.isShapeHidden(shapeId)) {
// shape won't render
}
Hidden shapes can still be selected via keyboard shortcuts like select-all. If you want to prevent this, filter the selection when it changes:
import { react, Tldraw } from 'tldraw'
import 'tldraw/tldraw.css'
export default function App() {
return (
<div style={{ position: 'fixed', inset: 0 }}>
<Tldraw
getShapeVisibility={(shape) => (shape.meta.hidden ? 'hidden' : 'inherit')}
onMount={(editor) => {
return react('filter hidden from selection', () => {
const selectedIds = editor.getSelectedShapeIds()
const visibleIds = selectedIds.filter((id) => !editor.isShapeHidden(id))
if (selectedIds.length !== visibleIds.length) {
editor.setSelectedShapes(visibleIds)
}
})
}}
/>
</div>
)
}
For examples using hidden shapes, see the collaboration private content example and the layer panel example.
Each shape has an opacity property (0 to 1) that controls its transparency. When shapes are nested, opacity multiplies down the hierarchy: a shape at 0.5 opacity inside a parent at 0.5 opacity renders at 0.25 opacity.
The editor calculates the cumulative opacity for each shape and passes it to the rendering layer. You can access the computed opacity through Editor#getRenderingShapes:
const renderingShapes = editor.getRenderingShapes()
for (const { shape, opacity } of renderingShapes) {
console.log(shape.id, opacity) // cumulative opacity
}
When shapes are being erased (the user is mid-gesture with the eraser tool), they render at 32% of their normal opacity. This gives visual feedback that the shapes will be deleted when the gesture completes. The editor handles this automatically—you don't need to implement anything for erasing feedback to work.
Editor#getRenderingShapes returns the shapes that should be rendered, along with their computed properties:
interface TLRenderingShape {
id: TLShapeId
shape: TLShape
util: ShapeUtil
index: number // z-index for the shape layer
backgroundIndex: number // z-index for the background layer
opacity: number // cumulative opacity
}
The index and backgroundIndex values control z-ordering. The editor uses CSS z-index rather than DOM ordering to position shapes visually. This keeps the DOM stable (shapes stay in ID order) and avoids expensive reflows when z-order changes.
For most use cases, you won't need to work with rendering shapes directly; the editor's canvas handles this. Use it if you're building custom rendering or need to know which shapes are visible at what opacity.