component
Sign
In-world text rendered in a world font (the one font-using component); falls back to the default bundled in @runek/core.
⌖ Explore the library in 3D → · View as Markdown
Add it
npx @runek/cli add sign
Pulls @react-three/drei@^10.7.7, @runek/core@^0.15.0.
Use it
import { Sign } from './runek/Sign'
<Sign position={[0, 0, 0]} />
Or as a node in a world file (worlds as data):
{ "type": "Sign", "props": { "position": [0, 0, 0] } }
Props
| Prop | Type | Default | Description |
|---|---|---|---|
children |
string |
required | The text to render. |
position |
[x, y, z] |
[0, 0, 0] |
Position [x, y, z] in units (1 unit = 1 m, Y-up). |
rotation |
[x, y, z] |
[0, 0, 0] |
Euler rotation [x, y, z] in radians. |
seed |
number |
Accepted for the component contract; this component has no seeded variation yet. | |
variant |
"display" | "body" |
"display" |
Which world font role to render in (display for titles/signage, body for labels). The world declares the actual face via <World fonts>; an undeclared role falls back to the font bundled in @runek/core. (Named variant, not role, so it doesn’t trip JSX a11y linters.) |
font |
string |
Explicit font URL, overriding the world role. | |
size |
number |
0.3 |
Cap height in units. |
color |
color |
palette accent |
Text color. Defaults to the world palette’s accent slot. |
maxWidth |
number |
Wrap width in units; omit to keep the text on one line. | |
letterSpacing |
number |
0 |
|
anchorX |
"left" | "center" | "right" |
"center" |
|
anchorY |
"top" | "bottom" | "middle" |
"middle" |
|
glow |
boolean |
false |
Soft colored halo around the glyphs, for a glow without bloom. |
outline |
color |
A crisp edge in this color, so the text reads against any background. Wins over glow. |
|
onBounds |
function (code only) |
Called with the laid-out text’s width and height, in world units, whenever it changes: for fitting a backdrop behind it. |
Registry manifest
registry manifest /r/components/sign.json Self-contained JSON: inlined source plus resolved dependencies, exactly whatrunek add sign fetches.
Browse the whole catalog in the gallery →.
Source (Sign.tsx)
import { Text } from '@react-three/drei'
import { useWorld, type Vec3, type WorldFonts } from '@runek/core'
import type { ReactNode } from 'react'
export interface SignProps {
/** The text to render. */
children: ReactNode
position?: Vec3
rotation?: Vec3
/** Accepted for the component contract; this component has no seeded variation yet. */
seed?: number
/**
* Which world font role to render in (`display` for titles/signage, `body`
* for labels). The world declares the actual face via `<World fonts>`; an
* undeclared role falls back to the font bundled in `@runek/core`. (Named
* `variant`, not `role`, so it doesn't trip JSX a11y linters.)
*/
variant?: keyof WorldFonts
/** Explicit font URL, overriding the world role. */
font?: string
/** Cap height in units. */
size?: number
/** Text color. Defaults to the world palette's `accent` slot. */
color?: string
/** Wrap width in units; omit to keep the text on one line. */
maxWidth?: number
letterSpacing?: number
anchorX?: 'left' | 'center' | 'right'
anchorY?: 'top' | 'middle' | 'bottom'
/** Soft colored halo around the glyphs, for a glow without bloom. */
glow?: boolean
/** A crisp edge in this color, so the text reads against any background. Wins over `glow`. */
outline?: string
/** Called with the laid-out text's width and height, in world units, whenever it changes:
* for fitting a backdrop behind it. */
onBounds?: (width: number, height: number) => void
}
/**
* In-world text. The one component that renders glyphs, and so the single
* carve-out to the no-assets rule (CONTRACT §4): it ships no font of its own,
* drawing only from the world's declared `fonts` or the default bundled in
* `@runek/core`. Decorative: it registers no colliders (mount it on a surface
* that has one, e.g. a Wall).
*/
export function Sign({
children,
position = [0, 0, 0],
rotation = [0, 0, 0],
variant = 'display',
font,
size = 0.3,
color,
maxWidth,
letterSpacing = 0,
anchorX = 'center',
anchorY = 'middle',
glow = false,
outline,
onBounds,
}: SignProps) {
const { unit, fonts, palette } = useWorld()
const face = font ?? fonts[variant]
const ink = color ?? palette.accent
return (
<Text
font={face}
position={position}
rotation={rotation}
fontSize={size * unit}
color={ink}
maxWidth={maxWidth !== undefined ? maxWidth * unit : undefined}
letterSpacing={letterSpacing}
anchorX={anchorX}
anchorY={anchorY}
outlineWidth={outline ? '9%' : glow ? 0.02 * unit : 0}
outlineColor={outline ?? ink}
outlineBlur={outline || !glow ? 0 : '45%'}
outlineOpacity={outline || !glow ? 1 : 0.5}
onSync={
onBounds
? (text: { textRenderInfo?: { blockBounds: number[] } }) => {
const b = text.textRenderInfo?.blockBounds
if (b) onBounds(b[2] - b[0], b[3] - b[1])
}
: undefined
}
>
{children}
</Text>
)
}