Files
upstream-mirror a774ac673a
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
2026-09-23 16:12:52 +05:00

144 lines
5.4 KiB
Plaintext

---
title: Visibility
status: published
author: steveruizok
date: 1/31/2026
keywords:
- culling
- visibility
- rendering
- performance
- opacity
- hidden
- viewport
---
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
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](?):
```ts
const culledIds = editor.getCulledShapes()
```
See [Culling](/sdk-features/culling) for how the spatial index decides what to cull and for the full set of culling methods.
## Hidden shapes
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.
```tsx
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:
- Canvas rendering
- Hit tests ([Editor#getShapeAtPoint](?), [Editor#getShapesAtPoint](?))
- [Editor#getRenderingShapes](?) and [Editor#getCurrentPageRenderingShapesSorted](?)
- Image exports and printing
- [Editor#getCurrentPageBounds](?) and [Editor#zoomToFit](?)
- Drop targets and automatic parenting of new shapes
You can check if a shape is hidden with [Editor#isShapeHidden](?):
```ts
if (editor.isShapeHidden(shapeId)) {
// shape won't render
}
```
### Preventing hidden shapes from being selected
Hidden shapes can still be selected via keyboard shortcuts like select-all. If you want to prevent this, filter the [selection](/sdk-features/selection) when it changes:
```tsx
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](/examples/collaboration/sync-private-content) and the [layer panel example](/examples/ui/layer-panel).
## Opacity
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](?):
```ts
const renderingShapes = editor.getRenderingShapes()
for (const { shape, opacity } of renderingShapes) {
console.log(shape.id, opacity) // cumulative opacity
}
```
### Erasing feedback
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.
## Rendering shapes
[Editor#getRenderingShapes](?) returns the shapes that should be rendered, along with their computed properties:
```ts
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.