Safe area
Format-aware title and action safety boundaries.
Customize
import {Scene, Video} from "odori";
import {SafeArea} from "../components/safe-area/safe-area";
<Video>
<Scene id="safe-area" duration="3s">
<SafeArea
guides={true}
inset={0}
label="safe area"
/>
</Scene>
</Video>Installation
pnpm odori add component safe-areanpx odori add component safe-areabunx odori add component safe-areaEdit the source in videos/components/safe-area/. Use odori diff to compare upstream changes and odori update to apply updates.
Copy these files into your project
videos/components/safe-area/safe-area.tsx
import type {ReactNode} from "react";
import {Fill, Easing, interpolate, useBrand, useFrame, useDesignScale, useLayout} from "odori";
export type SafeAreaGuidesProps = {
children?: ReactNode;
/** Draw the title and action boundaries. */
guides?: boolean;
/** Extra inset beyond the layout policy, in design pixels. */
inset?: number;
label?: string;
};
/**
* Keeps content inside the boundaries the layout declares, and can draw them.
* The numbers come from the resolved layout, so a 9:16 cut gets the safety it
* asked for rather than a value guessed at authoring time.
*/
export const SafeAreaGuides = ({children, guides = true, inset = 0, label = "safe area"}: SafeAreaGuidesProps) => {
const frame = useFrame();
const brand = useBrand();
const scale = useDesignScale();
const layout = useLayout();
const safe = layout.safeArea ?? {x: 80, y: 80};
const x = (safe.x + inset) * scale;
const y = (safe.y + inset) * scale;
const appear = interpolate(frame, [0, 18], [0, 1], {easing: Easing.standard});
return (
<Fill>
<Fill
style={{
alignItems: "center",
justifyContent: "center",
padding: `${y}px ${x}px`,
textAlign: "center",
}}
>
{children}
</Fill>
{guides ? (
<Fill style={{opacity: appear, pointerEvents: "none"}}>
<div
style={{
border: `${2 * scale}px solid ${brand.colors.accent}`,
inset: 0,
margin: `${y}px ${x}px`,
opacity: 0.55,
position: "absolute",
}}
/>
<span
style={{
color: brand.colors.muted,
fontFamily: brand.typography.mono,
fontSize: 22 * scale,
left: x,
position: "absolute",
top: Math.max(0, y - 34 * scale),
}}
>
{label} · {Math.round(safe.x + inset)} x {Math.round(safe.y + inset)}
</span>
</Fill>
) : null}
</Fill>
);
};
videos/components/safe-area/safe-area.preview.tsx
import {defineComponentPreview} from "odori/preview";
import {SafeAreaGuides} from "./safe-area";
export default defineComponentPreview({
title: "Safe area",
category: "Foundation",
description: "Format-aware title and action safety boundaries.",
component: SafeAreaGuides,
canvas: {width: 1920, height: 1080, duration: "3s"},
controls: {
guides: {type: "boolean", defaultValue: true},
inset: {type: "number", defaultValue: 0, min: 0, max: 160},
label: {type: "text", defaultValue: "safe area"},
},
examples: [
{name: "Guides", props: {guides: true, children: "Everything readable sits inside"}},
{name: "Composed", props: {guides: false, children: "Everything readable sits inside"}},
],
});
Add it to a scene
import {Scene, Video} from "odori";
import {SafeArea} from "../components/safe-area/safe-area";
<Video>
<Scene id="safe-area" duration="3s">
<SafeArea
guides={true}
inset={0}
label="safe area"
/>
</Scene>
</Video>Timing contract
Use these durations and content limits when composing a scene. Run odori test to check your video.
- Family
- Foundation
- Recommended duration
- 90 frames · 3s
- Minimum duration
- 24 frames · 0.8s
- Entrance
- 18 frames
- Exit
- 0 frames
- Reduced motion
- guides appear without fading
- Requires
- sans font, mono font
- Content limits
- none
Props
| Prop | Type | Default | Required |
|---|---|---|---|
children | ReactNode | — | — |
guidesDraw the title and action boundaries. | boolean | true | — |
insetExtra inset beyond the layout policy, in design pixels. | number | 0 | — |
label | string | "safe area" | — |
Related components
All componentsAbsolute stageFull-frame surface with stable clipping and positioning.Split layoutResponsive two-up composition for proof and explanation.StageThe shared backdrop: hairline grid, soft glow, and vignette.AccordionSections opening one at a time, the outgoing one closing as the next grows.Area chartFilled trend reveal for volume and cumulative change.Aurora veilFlowing curtains of colored light.