---
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 (
{
if (shape.meta.hidden) return 'hidden'
return 'inherit'
}}
/>
)
}
```
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 (
(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)
}
})
}}
/>
)
}
```
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.