Flex

A flex container with full control over direction, alignment and distribution.

ATRI

Sci-fiOngoing
<script setup lang="ts">
  import { Button, Card, Flex, Heading, Tag } from '@hina-ui/vue'
</script>

<template>
  <Card class="w-full max-w-lg">
    <Flex justify="between" align="center" wrap class="gap-3">
      <Flex direction="col" gap="xs">
        <Heading :level="3" size="md">ATRI</Heading>
        <Flex gap="xs">
          <Tag>Sci-fi</Tag>
          <Tag>Ongoing</Tag>
        </Flex>
      </Flex>
      <Flex gap="sm">
        <Button variant="soft" tone="neutral">Add to shelf</Button>
        <Button>Read</Button>
      </Flex>
    </Flex>
  </Card>
</template>
vue

Usage

import { Flex } from '@hina-ui/vue'
ts

Flex exposes all four flex properties: direction, align, justify and wrap. Stack and Inline are shorter where the direction is fixed; Flex is for switching direction or for full control over alignment.

<script setup lang="ts">
  import { Card, Flex } from '@hina-ui/vue'
</script>

<template>
  <Flex gap="sm">
    <Card class="bg-inset size-10" :padded="false" />
    <Card class="bg-inset size-10" :padded="false" />
    <Card class="bg-inset size-10" :padded="false" />
  </Flex>
</template>
vue

Examples

Direction

direction takes four values and defaults to a row.

row

1
2
3

row-reverse

1
2
3

col

1
2
3

col-reverse

1
2
3
<script setup lang="ts">
  import { Card, Flex, Stack, Text } from '@hina-ui/vue'

  const directions = ['row', 'row-reverse', 'col', 'col-reverse'] as const
</script>

<template>
  <Flex gap="lg" wrap>
    <Stack v-for="direction in directions" :key="direction" gap="xs">
      <Text tone="muted" size="sm">{{ direction }}</Text>
      <Flex :direction="direction" gap="sm" class="bg-inset rounded-md p-3">
        <Card class="bg-surface grid size-8 place-items-center text-sm" :padded="false">1</Card>
        <Card class="bg-surface grid size-8 place-items-center text-sm" :padded="false">2</Card>
        <Card class="bg-surface grid size-8 place-items-center text-sm" :padded="false">3</Card>
      </Flex>
    </Stack>
  </Flex>
</template>
vue

Gap

gap takes steps rather than arbitrary values. The md step follows the direction: a row takes --hn-inline-gap and a column --hn-stack-gap, so the spacing needs no adjustment when the direction changes. The other steps are the same either way: 0, 4, 8, 24 and 32 pixels.

A row takes 12 pixels at md

A column takes 16 pixels at md

<script setup lang="ts">
  import { Card, Flex, Stack, Text } from '@hina-ui/vue'
</script>

<template>
  <Flex gap="xl" wrap>
    <Stack gap="xs">
      <Text tone="muted" size="sm">A row takes 12 pixels at md</Text>
      <Flex gap="md" class="bg-inset rounded-md p-3">
        <Card class="bg-surface size-8" :padded="false" />
        <Card class="bg-surface size-8" :padded="false" />
        <Card class="bg-surface size-8" :padded="false" />
      </Flex>
    </Stack>
    <Stack gap="xs">
      <Text tone="muted" size="sm">A column takes 16 pixels at md</Text>
      <Flex direction="col" gap="md" class="bg-inset rounded-md p-3">
        <Card class="bg-surface size-8" :padded="false" />
        <Card class="bg-surface size-8" :padded="false" />
        <Card class="bg-surface size-8" :padded="false" />
      </Flex>
    </Stack>
  </Flex>
</template>
vue

Alignment and distribution

align works on the cross axis and justify on the main one. In a row align is vertical and justify horizontal; in a column they swap.

align works on the cross axis

start

center

end

stretch

justify works on the main axis

<script setup lang="ts">
  import { Card, Flex, Stack, Text } from '@hina-ui/vue'

  const aligns = ['start', 'center', 'end', 'stretch'] as const
  const justifies = ['start', 'center', 'end', 'between'] as const
</script>

<template>
  <Stack>
    <Stack gap="xs">
      <Text tone="muted" size="sm">align works on the cross axis</Text>
      <Flex gap="lg" wrap>
        <Stack v-for="align in aligns" :key="align" gap="xs">
          <Text tone="muted" size="sm">{{ align }}</Text>
          <Flex :align="align" gap="sm" class="bg-inset h-24 rounded-md p-3">
            <Card class="bg-surface h-8 w-8" :padded="false" />
            <Card class="bg-surface h-12 w-8" :padded="false" />
          </Flex>
        </Stack>
      </Flex>
    </Stack>
    <Stack gap="xs">
      <Text tone="muted" size="sm">justify works on the main axis</Text>
      <Flex direction="col" gap="sm">
        <Flex
          v-for="justify in justifies"
          :key="justify"
          :justify="justify"
          class="bg-inset w-64 rounded-md p-3"
        >
          <Card class="bg-surface size-8" :padded="false" />
          <Card class="bg-surface size-8" :padded="false" />
        </Flex>
      </Flex>
    </Stack>
  </Stack>
</template>
vue

Wrapping

Left unset, wrap does not wrap, which is what flex does on its own. Inline wraps by default; Flex makes no such assumption.

With wrap, items move to the next line

Without it they stay on one line

<script setup lang="ts">
  import { Card, Flex, Stack, Text } from '@hina-ui/vue'

  const items = Array.from({ length: 10 }, (_, i) => i + 1)
</script>

<template>
  <Stack class="w-full max-w-xs">
    <Stack gap="xs">
      <Text tone="muted" size="sm">With wrap, items move to the next line</Text>
      <Flex wrap gap="sm">
        <Card v-for="i in items" :key="i" class="bg-inset size-10" :padded="false" />
      </Flex>
    </Stack>
    <Stack gap="xs">
      <Text tone="muted" size="sm">Without it they stay on one line</Text>
      <Flex gap="sm" class="overflow-hidden">
        <Card v-for="i in items" :key="i" class="bg-inset size-10 shrink-0" :padded="false" />
      </Flex>
    </Stack>
  </Stack>
</template>
vue

Following breakpoints

When the direction or alignment differs between breakpoints, override it with Tailwind classes such as sm:flex-row. The props themselves do not take responsive objects.

ATRI

The cover sits above the text on a narrow screen and beside it on a wide one. Breakpoints override direction through Tailwind classes.

<script setup lang="ts">
  import { Card, Flex, Heading, Text } from '@hina-ui/vue'
</script>

<template>
  <Flex direction="col" gap="md" class="w-full max-w-lg sm:flex-row sm:items-center">
    <Card class="bg-inset h-24 shrink-0 sm:w-32" :padded="false" />
    <Flex direction="col" gap="xs">
      <Heading :level="3" size="md">ATRI</Heading>
      <Text tone="muted" size="sm">
        The cover sits above the text on a narrow screen and beside it on a wide one. Breakpoints
        override direction through Tailwind classes.
      </Text>
    </Flex>
  </Flex>
</template>
vue

Root element

as accepts a tag name or Vue component, such as as="section", :as="RouterLink" or :as="NuxtLink". Import the component or obtain it through resolveComponent; object, functional and async components are supported. Layout classes, extra attributes, event listeners and the default slot are forwarded without an extra wrapper. Custom components must forward attributes and classes to their rendered root.

API

Prop
Type
Default
Description
direction
'row' | 'col' | 'row-reverse' | 'col-reverse'
'row'
Main-axis direction
gap
'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'
—
Space between the children
align
'start' | 'center' | 'end' | 'baseline' | 'stretch'
—
Cross-axis alignment
justify
'start' | 'center' | 'end' | 'between' | 'around' | 'evenly'
—
Main-axis distribution
wrap
boolean
—
Whether children wrap
as
string | Component
'div'
The rendered tag or Vue component
class
string
—
Classes appended to the container
Slot
Description
default
The children