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'

  const browser = typeof window !== 'undefined'

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

  let selected: string = browser ? sessionStorage.selected || '' : ''
  let components: Example[] = []

  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)

  $: example = components.find((e) => e.name === selected)
  $: if (browser && selected.length) sessionStorage.selected = selected
</script>

<svelte:window
  on:keydown={(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>
        <svelte:component this={example.dom} />
      </div>
    </div>

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

        <svelte:component this={example.threlte} />
      </Canvas>
    </div>
  </div>
{/if}

<nav>
  <select bind:value={selected}>
    {#each components as { 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 { DEG2RAD } from 'three/src/math/MathUtils.js'

  export let size = 1
  export let thickness = 0.02
  export let arrows = false
  export let hideX = false
  export let hideY = false
  export let hideZ = false

  $: showAny = !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]}
      on:create={({ ref }) => {
        ref.rotateZ(90 * DEG2RAD)
      }}
    />
    <T.MeshBasicMaterial color="red" />

    {#if arrows}
      <T.Mesh position.x={size / 2 + thickness * 3}>
        <T.ConeGeometry
          args={[thickness * 3, thickness * 6]}
          on:create={({ ref }) => {
            ref.rotateZ(-90 * 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]}
      on:create={({ ref }) => {
        ref.rotateX(90 * DEG2RAD)
      }}
    />
    <T.MeshBasicMaterial color="blue" />

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

  export let color: string = 'white'
  export let height = 1
  export let width = 1
  export let depth = 0
</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
  class="red"
  style="width: 300px; height: 300px; display: flex;"
>
  <div
    class="yellow"
    style="width: 44px; height: 44px;"
  />

  <div
    class="blue"
    style="width: 44px; height: 44px;"
  />
</div>

<style lang="postcss">
  @import '../../colors.postcss';
</style>
<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
  class="red"
  style="width: 300px; height: 300px; display: flex;"
>
  <div
    class="yellow"
    style="width: 44px; height: 44px;"
  />

  <div
    class="blue"
    style="width: 44px; height: 44px; flex: 1"
  />
</div>

<style lang="postcss">
  @import '../../colors.postcss';
</style>
<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}
    let:width
  >
    <Plane
      color="blue"
      {width}
      height={44}
      depth={1}
    />
  </Box>
</Flex>
<div
  class="red"
  style="width: 300px; height: 300px; display: flex; justify-content: flex-end;"
>
  <div
    class="yellow"
    style="width: 44px; height: 44px;"
  />

  <div
    class="blue"
    style="width: 44px; height: 44px;"
  />
</div>

<style lang="postcss">
  @import '../../colors.postcss';
</style>
<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
  class="red"
  style="width: 300px; height: 300px; display: flex; justify-content: flex-end; align-items: flex-end;"
>
  <div
    class="yellow"
    style="width: 44px; height: 44px;"
  />

  <div
    class="blue"
    style="width: 44px; height: 44px;"
  />
</div>

<style lang="postcss">
  @import '../../colors.postcss';
</style>
<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
  class="red"
  style="width: 300px; height: 300px; display: flex; justify-content: center; align-items: center; gap: 20px"
>
  <div
    class="yellow"
    style="width: 44px; height: 44px;"
  />

  <div
    class="blue"
    style="width: 44px; height: 44px;"
  />
</div>

<style lang="postcss">
  @import '../../colors.postcss';
</style>
<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
  class="red"
  style="width: 300px; height: 300px; display: flex; justify-content: center; align-items: stretch; gap: 20px"
>
  <div
    class="yellow"
    style="width: 44px;"
  />

  <div
    class="blue"
    style="width: 44px;"
  />
</div>

<style lang="postcss">
  @import '../../colors.postcss';
</style>
<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"
    let:width
    let:height
  >
    <Plane
      color="yellow"
      {width}
      {height}
      depth={1}
    />
  </Box>

  <Box
    width={44}
    height="auto"
    let:width
    let:height
  >
    <Plane
      color="blue"
      {width}
      {height}
      depth={1}
    />
  </Box>
</Flex>
<div
  class="red"
  style="width: 300px; height: 300px; display: flex; justify-content: center; align-items: stretch; gap: 20px; padding: 20px; box-sizing: border-box;"
>
  <div
    class="yellow"
    style="width: auto; height: auto; flex: 1;"
  />

  <div
    class="blue"
    style="width: auto; height: 200px; flex: 0.5"
  />
</div>

<style lang="postcss">
  @import '../../colors.postcss';
</style>
<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}
    let:width
    let:height
  >
    <Plane
      color="yellow"
      {width}
      {height}
      depth={1}
    />
  </Box>

  <Box
    width="auto"
    height={200}
    flex={0.5}
    let:width
    let:height
  >
    <Plane
      color="blue"
      {width}
      {height}
      depth={1}
    />
  </Box>
</Flex>
<div
  class="red"
  style="width: 300px; height: 300px; display: flex; gap: 10px; padding: 10px; box-sizing: border-box;"
>
  <div
    class="yellow"
    style="width: 44px; height: 44px;"
  />

  <div
    class="blue"
    style="width: 44px; height: 44px; flex: 1"
  />
</div>

<style lang="postcss">
  @import '../../colors.postcss';
</style>
<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"
    let:width
    let:height
  >
    <Plane
      {width}
      {height}
      color="yellow"
      depth={1}
    />
  </Box>

  <Box
    class="flex-1"
    let:width
  >
    <Plane
      color="blue"
      {width}
      height={44}
      depth={1}
    />
  </Box>
</Flex>
<div
  class="red"
  style="width: 300px; height: 300px; display: flex; justify-content: center; align-items: stretch; gap: 20px; padding: 20px; box-sizing: border-box;"
>
  <div
    class="yellow"
    style="width: auto; height: auto; flex: 1;"
  />

  <div
    class="blue"
    style="width: auto; height: auto; flex: 0.5; display: flex; padding: 20px; align-items: stretch;"
  >
    <div
      class="pink"
      style="height: 44px; width: auto; flex: 1;"
    />
    <div
      class="hotpink"
      style="height: 44px; width: auto; flex: 1; align-self: flex-end;"
    />
  </div>
</div>

<style lang="postcss">
  @import '../../colors.postcss';
</style>
<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}
    let:width
    let:height
  >
    <Plane
      color="yellow"
      {width}
      {height}
      depth={1}
    />
  </Box>

  <Box
    width="auto"
    height="auto"
    flex={0.5}
    alignItems="Stretch"
    padding={20}
    let:width
    let:height
  >
    <Plane
      color="blue"
      {width}
      {height}
      depth={1}
    />

    <Box
      flex={1}
      width="auto"
      height={44}
      let:width
      let:height
    >
      <Plane
        color="pink"
        {width}
        {height}
        depth={2}
      />
    </Box>

    <Box
      flex={1}
      width="auto"
      height={44}
      alignSelf="FlexEnd"
      let:width
      let:height
    >
      <Plane
        color="hotpink"
        {width}
        {height}
        depth={2}
      />
    </Box>
  </Box>
</Flex>
<div
  class="red"
  style="width: 300px; height: 300px; display: flex; justify-content: center; align-items: stretch; gap: 20px; padding: 20px; box-sizing: border-box;"
>
  <div
    class="yellow"
    style="width: auto; height: auto; flex: 1; display: flex; flex-direction: column; padding: 20px; gap: 20px"
  >
    <div
      class="fuchsia"
      style="height: auto; width: auto; flex: 1;"
    />

    <div
      class="orange"
      style="height: auto; width: auto; flex: 1; display: flex; justify-content: center; align-items: center;"
    >
      <div
        class="red"
        style="height: 44px; width: 44px;"
      />
    </div>
  </div>

  <div
    class="blue"
    style="width: auto; height: auto; flex: 0.5; display: flex; padding: 20px; align-items: stretch;"
  >
    <div
      class="pink"
      style="height: 44px; width: auto; flex: 1;"
    />
    <div
      class="hotpink"
      style="height: 44px; width: auto; flex: 1; align-self: flex-end;"
    />
  </div>
</div>

<style lang="postcss">
  @import '../../colors.postcss';
</style>
<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"
    let:width
    let:height
  >
    <Plane
      color="yellow"
      {width}
      {height}
      depth={1}
    />

    <Box
      flex={1}
      width="auto"
      height="auto"
      let:width
      let:height
    >
      <Plane
        color="fuchsia"
        {width}
        {height}
        depth={2}
      />
    </Box>

    <Box
      flex={1}
      width="auto"
      height="auto"
      justifyContent="Center"
      alignItems="Center"
      let:width
      let:height
    >
      <Plane
        color="orange"
        {width}
        {height}
        depth={2}
      />

      <Box
        width={44}
        height={44}
        let:width
        let:height
      >
        <Plane
          color="red"
          {width}
          {height}
          depth={3}
        />
      </Box>
    </Box>
  </Box>

  <Box
    width="auto"
    height="auto"
    flex={0.5}
    alignItems="Stretch"
    padding={20}
    let:width
    let:height
  >
    <Plane
      color="blue"
      {width}
      {height}
      depth={1}
    />

    <Box
      flex={1}
      width="auto"
      height={44}
      let:width
      let:height
    >
      <Plane
        color="pink"
        {width}
        {height}
        depth={2}
      />
    </Box>

    <Box
      flex={1}
      width="auto"
      height={44}
      alignSelf="FlexEnd"
      let:width
      let:height
    >
      <Plane
        color="hotpink"
        {width}
        {height}
        depth={2}
      />
    </Box>
  </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]}
  on:create={({ ref }) => ref.lookAt(0, 0, 0)}
>
  <OrbitControls zoomToCursor />
</T.OrthographicCamera>

<T.DirectionalLight />