threlte logo
@threlte/flex

Examples

The package @threlte/flex is well suited for building responsive layouts. Especially in an XR context, where using the DOM is (currently) not possible you have to rely on solutions that are native to WebGL.

The following examples show how to use the @threlte/flex package to build responsive layouts in WebGL.

The left side is the CSS implementation, the right side is using @threlte/flex.

<script lang="ts">
  import { Canvas } from '@threlte/core'
  import { NoToneMapping } from 'three'
  import { onMount } from 'svelte'
  import Common from './examples/Common.svelte'

  type Example = {
    name: string
    dom: ConstructorOfATypedSvelteComponent
    threlte: ConstructorOfATypedSvelteComponent
  }

  let selected: string = $state('')
  let components: Example[] = $state([])

  const load = () => {
    const modules = (import.meta as any).glob('./examples/*/*.svelte', { eager: true }) as Record<
      string,
      any
    >
    components = Object.entries(modules).reduce(
      (acc, [key, module]) => {
        const name = key.split('/')[2]
        if (!name) throw new Error(`No name for ${key}`)
        const isDom = key.includes('Dom')
        const example = acc.find((e) => e.name === name)
        if (example) {
          if (isDom) {
            example.dom = (module as any).default
          } else {
            example.threlte = (module as any).default
          }
        } else {
          acc.push({
            name,
            dom: isDom ? (module as any).default : undefined,
            threlte: isDom ? undefined : (module as any).default
          })
        }
        return acc
      },
      [] as unknown as typeof components
    )
    selected ||= components[0]!.name
  }

  onMount(load)

  let example = $derived(components.find((e) => e.name === selected))
  $effect(() => {
    if (selected.length) sessionStorage.selected = selected
  })
</script>

<svelte:window
  onkeydown={(event) => {
    if (event.key === 'ArrowLeft') {
      const index = components.findIndex((e) => e.name === selected)
      selected = components[index - 1]?.name || selected
    } else if (event.key === 'ArrowRight') {
      const index = components.findIndex((e) => e.name === selected)
      selected = components[index + 1]?.name || selected
    }
  }}
/>

{#if example}
  <div class="example-view split-view">
    <div class="dom">
      <div>
        <example.dom />
      </div>
    </div>

    <div class="threlte">
      <Canvas toneMapping={NoToneMapping}>
        <Common />

        <example.threlte />
      </Canvas>
    </div>
  </div>
{/if}

<nav>
  <select bind:value={selected}>
    {#each components as { name } (name)}
      <option value={name}>{name}</option>
    {/each}
  </select>
</nav>

<style>
  :global(body) {
    margin: 0;
  }

  nav {
    padding: 20px;
    position: relative;
  }

  .split-view {
    width: 100vw;
    display: flex;
    flex-direction: row;
  }

  .split-view > * {
    flex: 1;
  }

  .split-view > :nth-child(2) {
    border-left: 1px solid #404550;
  }

  .example-view {
    position: absolute;
    top: 0;
    height: 100vh;
  }

  .threlte {
    height: 100%;
  }

  .dom {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
  }
</style>
<script lang="ts">
  import { T } from '@threlte/core'
  import { MathUtils } from 'three'

  interface Props {
    size?: number
    thickness?: number
    arrows?: boolean
    hideX?: boolean
    hideY?: boolean
    hideZ?: boolean
  }

  let {
    size = 1,
    thickness = 0.02,
    arrows = false,
    hideX = false,
    hideY = false,
    hideZ = false
  }: Props = $props()

  let showAny = $derived(!hideX || !hideY || !hideZ)
</script>

{#if showAny}
  <T.Mesh>
    <T.SphereGeometry args={[thickness * 2]} />
    <T.MeshBasicMaterial color="white" />
  </T.Mesh>
{/if}

<!-- X Axis -->
{#if !hideX}
  <T.Mesh position.x={size / 2}>
    <T.CylinderGeometry
      args={[thickness, thickness, size]}
      oncreate={(ref) => {
        ref.rotateZ(90 * MathUtils.DEG2RAD)
      }}
    />
    <T.MeshBasicMaterial color="red" />

    {#if arrows}
      <T.Mesh position.x={size / 2 + thickness * 3}>
        <T.ConeGeometry
          args={[thickness * 3, thickness * 6]}
          oncreate={(ref) => {
            ref.rotateZ(-90 * MathUtils.DEG2RAD)
          }}
        />
        <T.MeshBasicMaterial color="red" />
      </T.Mesh>
    {/if}
  </T.Mesh>
{/if}

<!-- Y Axis -->
{#if !hideY}
  <T.Mesh position.y={size / 2}>
    <T.CylinderGeometry args={[thickness, thickness, size]} />
    <T.MeshBasicMaterial color="green" />

    {#if arrows}
      <T.Mesh position.y={size / 2 + thickness * 3}>
        <T.ConeGeometry args={[thickness * 3, thickness * 6]} />
        <T.MeshBasicMaterial color="green" />
      </T.Mesh>
    {/if}
  </T.Mesh>
{/if}

<!-- Z Axis -->
{#if !hideZ}
  <T.Mesh position.z={size / 2}>
    <T.CylinderGeometry
      args={[thickness, thickness, size]}
      oncreate={(ref) => {
        ref.rotateX(90 * MathUtils.DEG2RAD)
      }}
    />
    <T.MeshBasicMaterial color="blue" />

    {#if arrows}
      <T.Mesh position.z={size / 2 + thickness * 3}>
        <T.ConeGeometry
          args={[thickness * 3, thickness * 6]}
          oncreate={(ref) => {
            ref.rotateX(90 * MathUtils.DEG2RAD)
          }}
        />
        <T.MeshBasicMaterial color="blue" />
      </T.Mesh>
    {/if}
  </T.Mesh>
{/if}
<script lang="ts">
  import { T } from '@threlte/core'

  interface Props {
    color?: string
    height?: number
    width?: number
    depth?: number
  }

  let { color = 'white', height = 1, width = 1, depth = 0 }: Props = $props()
</script>

<T.Mesh
  position.z={depth * 20}
  renderOrder={depth}
>
  <T.PlaneGeometry args={[width, height]} />
  <T.MeshBasicMaterial
    {color}
    transparent
    opacity={0.5}
  />
</T.Mesh>
.red {
  background-color: color-mix(in srgb, red 50%, transparent 50%);
}

.blue {
  background-color: color-mix(in srgb, blue 50%, transparent 50%);
}

.green {
  background-color: color-mix(in srgb, green 50%, transparent 50%);
}

.yellow {
  background-color: color-mix(in srgb, yellow 50%, transparent 50%);
}

.purple {
  background-color: color-mix(in srgb, purple 50%, transparent 50%);
}

.orange {
  background-color: color-mix(in srgb, orange 50%, transparent 50%);
}

.pink {
  background-color: color-mix(in srgb, pink 50%, transparent 50%);
}

.brown {
  background-color: color-mix(in srgb, brown 50%, transparent 50%);
}

.fuchsia {
  background-color: color-mix(in srgb, fuchsia 50%, transparent 50%);
}

.pink {
  background-color: color-mix(in srgb, pink 50%, transparent 50%);
}

.hotpink {
  background-color: color-mix(in srgb, hotpink 50%, transparent 50%);
}

.cyan {
  background-color: color-mix(in srgb, cyan 50%, transparent 50%);
}
<div
  style="
    width: 300px;
    height: 300px;
    display: flex;
    background-color: color-mix(in srgb, red 50%, transparent 50%);
  "
>
  <div
    style="
      width: 44px;
      height: 44px;
      background-color: color-mix(in srgb, yellow 50%, transparent 50%);
    "
  ></div>

  <div
    style="
      width: 44px;
      height: 44px;
      background-color: color-mix(in srgb, blue 50%, transparent 50%);
    "
  ></div>
</div>
<script lang="ts">
  import { Flex, Box } from '@threlte/flex'
  import Plane from '../../Plane.svelte'
</script>

<Plane
  width={300}
  height={300}
  color="red"
/>

<Flex
  width={300}
  height={300}
>
  <Box>
    <Plane
      color="yellow"
      width={44}
      height={44}
      depth={1}
    />
  </Box>
  <Box>
    <Plane
      color="blue"
      width={44}
      height={44}
      depth={1}
    />
  </Box>
</Flex>
<div
  style="
    width: 300px;
    height: 300px;
    display: flex;
    background-color: color-mix(in srgb, red 50%, transparent 50%);
  "
>
  <div
    style="
      width: 44px;
      height: 44px;
      background-color: color-mix(in srgb, yellow 50%, transparent 50%);
    "
  ></div>

  <div
    style="
      width: 44px;
      height: 44px;
      flex: 1;
      background-color: color-mix(in srgb, blue 50%, transparent 50%);
    "
  ></div>
</div>
<script lang="ts">
  import { Flex, Box } from '@threlte/flex'
  import Plane from '../../Plane.svelte'
</script>

<Plane
  width={300}
  height={300}
  color="red"
/>

<Flex
  width={300}
  height={300}
>
  <Box>
    <Plane
      color="yellow"
      width={44}
      height={44}
      depth={1}
    />
  </Box>

  <Box flex={1}>
    {#snippet children({ width })}
      <Plane
        color="blue"
        {width}
        height={44}
        depth={1}
      />
    {/snippet}
  </Box>
</Flex>
<div
  style="
    width: 300px;
    height: 300px;
    display: flex;
    justify-content: flex-end;
    background-color: color-mix(in srgb, red 50%, transparent 50%);
  "
>
  <div
    style="
      width: 44px;
      height: 44px;
      background-color: color-mix(in srgb, yellow 50%, transparent 50%);
    "
  ></div>

  <div
    style="
      width: 44px;
      height: 44px;
      background-color: color-mix(in srgb, blue 50%, transparent 50%);
    "
  ></div>
</div>
<script lang="ts">
  import { Flex, Box } from '@threlte/flex'
  import Plane from '../../Plane.svelte'
</script>

<Plane
  width={300}
  height={300}
  color="red"
/>

<Flex
  width={300}
  height={300}
  justifyContent="FlexEnd"
>
  <Box>
    <Plane
      color="yellow"
      width={44}
      height={44}
      depth={1}
    />
  </Box>

  <Box>
    <Plane
      color="blue"
      width={44}
      height={44}
      depth={1}
    />
  </Box>
</Flex>
<div
  style="
    width: 300px;
    height: 300px;
    display: flex;
    justify-content: flex-end;
    align-items: flex-end;
    background-color: color-mix(in srgb, red 50%, transparent 50%);
  "
>
  <div
    style="
      width: 44px; 
      height: 44px;
      background-color: color-mix(in srgb, yellow 50%, transparent 50%);
    "
  ></div>

  <div
    style="
      width: 44px;
      height: 44px;
      background-color: color-mix(in srgb, blue 50%, transparent 50%);
    "
  ></div>
</div>
<script lang="ts">
  import { Flex, Box } from '@threlte/flex'
  import Plane from '../../Plane.svelte'
</script>

<Plane
  width={300}
  height={300}
  color="red"
/>

<Flex
  width={300}
  height={300}
  justifyContent="FlexEnd"
  alignItems="FlexEnd"
>
  <Box>
    <Plane
      color="yellow"
      width={44}
      height={44}
      depth={1}
    />
  </Box>

  <Box>
    <Plane
      color="blue"
      width={44}
      height={44}
      depth={1}
    />
  </Box>
</Flex>
<div
  style="
    width: 300px;
    height: 300px;
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 20px;
    background-color: color-mix(in srgb, red 50%, transparent 50%);
  "
>
  <div
    style="
      width: 44px;
      height: 44px;
      background-color: color-mix(in srgb, yellow 50%, transparent 50%);
    "
  ></div>

  <div
    style="
      width: 44px;
      height: 44px;
      background-color: color-mix(in srgb, blue 50%, transparent 50%);
    "
  ></div>
</div>
<script lang="ts">
  import { Flex, Box } from '@threlte/flex'
  import Plane from '../../Plane.svelte'
</script>

<Plane
  width={300}
  height={300}
  color="red"
/>

<Flex
  width={300}
  height={300}
  justifyContent="Center"
  alignItems="Center"
  gap={20}
>
  <Box>
    <Plane
      color="yellow"
      width={44}
      height={44}
      depth={1}
    />
  </Box>

  <Box>
    <Plane
      color="blue"
      width={44}
      height={44}
      depth={1}
    />
  </Box>
</Flex>
<div
  style="
    width: 300px;
    height: 300px;
    display: flex;
    justify-content: center;
    align-items: stretch;
    gap: 20px;
    background-color: color-mix(in srgb, red 50%, transparent 50%);
  "
>
  <div
    style="
      width: 44px;
      background-color: color-mix(in srgb, yellow 50%, transparent 50%);
    "
  ></div>

  <div
    style="
      width: 44px;
      background-color: color-mix(in srgb, blue 50%, transparent 50%);
    "
  ></div>
</div>
<script lang="ts">
  import { Flex, Box } from '@threlte/flex'
  import Plane from '../../Plane.svelte'
</script>

<Plane
  width={300}
  height={300}
  color="red"
/>

<Flex
  width={300}
  height={300}
  justifyContent="Center"
  alignItems="Stretch"
  gap={20}
>
  <Box
    width={44}
    height="auto"
  >
    {#snippet children({ width, height })}
      <Plane
        color="yellow"
        {width}
        {height}
        depth={1}
      />
    {/snippet}
  </Box>

  <Box
    width={44}
    height="auto"
  >
    {#snippet children({ width, height })}
      <Plane
        color="blue"
        {width}
        {height}
        depth={1}
      />
    {/snippet}
  </Box>
</Flex>
<div
  style="
    width: 300px;
    height: 300px;
    display: flex;
    justify-content: center;
    align-items: stretch;
    gap: 20px;
    padding: 20px;
    box-sizing: border-box;
    background-color: color-mix(in srgb, red 50%, transparent 50%);
  "
>
  <div
    style="
      width: auto;
      height: auto;
      flex: 1;
      background-color: color-mix(in srgb, yellow 50%, transparent 50%);
    "
  ></div>

  <div
    style="
      width: auto;
      height: 200px;
      flex: 0.5;
      background-color: color-mix(in srgb, blue 50%, transparent 50%);
    "
  ></div>
</div>
<script lang="ts">
  import { Flex, Box } from '@threlte/flex'
  import Plane from '../../Plane.svelte'
</script>

<Plane
  width={300}
  height={300}
  color="red"
/>

<Flex
  width={300}
  height={300}
  justifyContent="Center"
  alignItems="Stretch"
  gap={20}
  padding={20}
>
  <Box
    width="auto"
    height="auto"
    flex={1}
  >
    {#snippet children({ width, height })}
      <Plane
        color="yellow"
        {width}
        {height}
        depth={1}
      />
    {/snippet}
  </Box>

  <Box
    width="auto"
    height={200}
    flex={0.5}
  >
    {#snippet children({ width, height })}
      <Plane
        color="blue"
        {width}
        {height}
        depth={1}
      />
    {/snippet}
  </Box>
</Flex>
<div
  style="
    width: 300px;
    height: 300px;
    display: flex;
    gap: 10px;
    padding: 10px;
    box-sizing: border-box;
    background-color: color-mix(in srgb, red 50%, transparent 50%);
  "
>
  <div
    style="
      width: 44px;
      height: 44px;
      background-color: color-mix(in srgb, yellow 50%, transparent 50%);
    "
  ></div>

  <div
    style="
      width: 44px; 
      height: 44px; 
      flex: 1;
      background-color: color-mix(in srgb, blue 50%, transparent 50%);
    "
  ></div>
</div>
<script lang="ts">
  import { Flex, Box, tailwindParser } from '@threlte/flex'
  import Plane from '../../Plane.svelte'
</script>

<Plane
  width={300}
  height={300}
  color="red"
/>

<Flex
  width={300}
  height={300}
  classParser={tailwindParser}
  class="gap-10 p-10"
>
  <Box class="h-44 w-44">
    {#snippet children({ width, height })}
      <Plane
        {width}
        {height}
        color="yellow"
        depth={1}
      />
    {/snippet}
  </Box>

  <Box class="flex-1">
    {#snippet children({ width })}
      <Plane
        color="blue"
        {width}
        height={44}
        depth={1}
      />
    {/snippet}
  </Box>
</Flex>
<div
  style="
    width: 300px;
    height: 300px;
    display: flex;
    justify-content: center;
    align-items: stretch;
    gap: 20px;
    padding: 20px;
    box-sizing: border-box;
    background-color: color-mix(in srgb, red 50%, transparent 50%);
  "
>
  <div
    style="
      width: auto;
      height: auto;
      flex: 1;
      background-color: color-mix(in srgb, yellow 50%, transparent 50%);
    "
  ></div>

  <div
    style="
      width: auto;
      height: auto;
      flex: 0.5;
      display: flex;
      padding: 20px;
      align-items: stretch;
      background-color: color-mix(in srgb, blue 50%, transparent 50%);
    "
  >
    <div
      style="
        height: 44px;
        width: auto;
        flex: 1;
        background-color: color-mix(in srgb, pink 50%, transparent 50%);
      "
    ></div>
    <div
      style="
        height: 44px;
        width: auto;
        flex: 1;
        align-self: flex-end;
        background-color: color-mix(in srgb, hotpink 50%, transparent 50%);
      "
    ></div>
  </div>
</div>
<script lang="ts">
  import { Flex, Box } from '@threlte/flex'
  import Plane from '../../Plane.svelte'
</script>

<Plane
  width={300}
  height={300}
  color="red"
/>

<Flex
  width={300}
  height={300}
  justifyContent="Center"
  alignItems="Stretch"
  gap={20}
  padding={20}
>
  <Box
    width="auto"
    height="auto"
    flex={1}
  >
    {#snippet children({ width, height })}
      <Plane
        color="yellow"
        {width}
        {height}
        depth={1}
      />
    {/snippet}
  </Box>

  <Box
    width="auto"
    height="auto"
    flex={0.5}
    alignItems="Stretch"
    padding={20}
  >
    {#snippet children({ width, height })}
      <Plane
        color="blue"
        {width}
        {height}
        depth={1}
      />

      <Box
        flex={1}
        width="auto"
        height={44}
      >
        {#snippet children({ width, height })}
          <Plane
            color="pink"
            {width}
            {height}
            depth={2}
          />
        {/snippet}
      </Box>

      <Box
        flex={1}
        width="auto"
        height={44}
        alignSelf="FlexEnd"
      >
        {#snippet children({ width, height })}
          <Plane
            color="hotpink"
            {width}
            {height}
            depth={2}
          />
        {/snippet}
      </Box>
    {/snippet}
  </Box>
</Flex>
<div
  style="
    width: 300px;
    height: 300px;
    display: flex;
    justify-content: center;
    align-items: stretch;
    gap: 20px;
    padding: 20px;
    box-sizing: border-box;
    background-color: color-mix(in srgb, red 50%, transparent 50%);
  "
>
  <div
    style="
      width: auto;
      height: auto;
      flex: 1;
      display: flex;
      flex-direction: column;
      padding: 20px;
      gap: 20px;
      background-color: color-mix(in srgb, yellow 50%, transparent 50%);
    "
  >
    <div
      style="
        height: auto;
        width: auto;
        flex: 1;
        background-color: color-mix(in srgb, fuchsia 50%, transparent 50%);
      "
    ></div>

    <div
      style="
        height: auto;
        width: auto;
        flex: 1;
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: color-mix(in srgb, orange 50%, transparent 50%);
      "
    >
      <div
        style="
          height: 44px;
          width: 44px;
          background-color: color-mix(in srgb, red 50%, transparent 50%);
        "
      ></div>
    </div>
  </div>

  <div
    style="
      width: auto;
      height: auto;
      flex: 0.5;
      display: flex;
      padding: 20px;
      align-items: stretch;
      background-color: color-mix(in srgb, blue 50%, transparent 50%);
    "
  >
    <div
      style="
        height: 44px;
        width: auto;
        flex: 1;
        background-color: color-mix(in srgb, pink 50%, transparent 50%);
      "
    ></div>
    <div
      style="
        height: 44px;
        width: auto;
        flex: 1;
        align-self: flex-end;
        background-color: color-mix(in srgb, hotpink 50%, transparent 50%);
      "
    ></div>
  </div>
</div>
<script lang="ts">
  import { Flex, Box } from '@threlte/flex'
  import Plane from '../../Plane.svelte'
</script>

<Plane
  width={300}
  height={300}
  color="red"
/>

<Flex
  width={300}
  height={300}
  justifyContent="Center"
  alignItems="Stretch"
  gap={20}
  padding={20}
>
  <Box
    width="auto"
    height="auto"
    flex={1}
    gap={20}
    padding={20}
    flexDirection="Column"
  >
    {#snippet children({ width, height })}
      <Plane
        color="yellow"
        {width}
        {height}
        depth={1}
      />

      <Box
        flex={1}
        width="auto"
        height="auto"
      >
        {#snippet children({ width, height })}
          <Plane
            color="fuchsia"
            {width}
            {height}
            depth={2}
          />
        {/snippet}
      </Box>

      <Box
        flex={1}
        width="auto"
        height="auto"
        justifyContent="Center"
        alignItems="Center"
      >
        {#snippet children({ width, height })}
          <Plane
            color="orange"
            {width}
            {height}
            depth={2}
          />

          <Box
            width={44}
            height={44}
          >
            {#snippet children({ width, height })}
              <Plane
                color="red"
                {width}
                {height}
                depth={3}
              />
            {/snippet}
          </Box>
        {/snippet}
      </Box>
    {/snippet}
  </Box>

  <Box
    width="auto"
    height="auto"
    flex={0.5}
    alignItems="Stretch"
    padding={20}
  >
    {#snippet children({ width, height })}
      <Plane
        color="blue"
        {width}
        {height}
        depth={1}
      />

      <Box
        flex={1}
        width="auto"
        height={44}
      >
        {#snippet children({ width, height })}
          <Plane
            color="pink"
            {width}
            {height}
            depth={2}
          />
        {/snippet}
      </Box>

      <Box
        flex={1}
        width="auto"
        height={44}
        alignSelf="FlexEnd"
      >
        {#snippet children({ width, height })}
          <Plane
            color="hotpink"
            {width}
            {height}
            depth={2}
          />
        {/snippet}
      </Box>
    {/snippet}
  </Box>
</Flex>
<script lang="ts">
  import { T } from '@threlte/core'
  import { OrbitControls } from '@threlte/extras'
</script>

<T.OrthographicCamera
  makeDefault
  near={44}
  far={4400}
  position={[0, 0, 3000]}
  oncreate={(ref) => ref.lookAt(0, 0, 0)}
>
  <OrbitControls zoomToCursor />
</T.OrthographicCamera>

<T.DirectionalLight />