---
name: shipping-address
title: Shipping Address
url: https://ui.frontic.com/components/sections/shipping-address.md
tier: sections
install: npx @frontic/ui add shipping-address
files:
  - components/sections/shipping-address/ShippingAddress.vue
  - components/sections/shipping-address/index.ts
  - components/sections/shipping-address/types.ts
registryDependencies:
  - badge
  - button
  - card
  - checkbox
  - field
  - input
  - native-select
dependencies:
  - "@lucide/vue"
---

# Shipping Address

Checkout address card with a step badge, name, street, postcode, city and country fields, a same-as-billing checkbox, and a continue button.

## Install

```sh
npx @frontic/ui add shipping-address
```

Built from: [Badge](https://ui.frontic.com/components/ui/badge.md), [Button](https://ui.frontic.com/components/ui/button.md), [Card](https://ui.frontic.com/components/ui/card.md), [Checkbox](https://ui.frontic.com/components/ui/checkbox.md), [Field](https://ui.frontic.com/components/ui/field.md), [Input](https://ui.frontic.com/components/ui/input.md), [Native Select](https://ui.frontic.com/components/ui/native-select.md)

npm packages: `@lucide/vue`

## Examples

```vue
<script setup lang="ts">
import { ref } from 'vue'
import { ShippingAddress, type CountryOption } from '@/components/sections/shipping-address'

const countries: CountryOption[] = [
  { value: 'de', label: 'Germany' },
  { value: 'at', label: 'Austria' },
  { value: 'ch', label: 'Switzerland' },
  { value: 'it', label: 'Italy' },
  { value: 'nl', label: 'Netherlands' },
]

const firstName = ref('Lena')
const lastName = ref('Huber')
const street = ref('Lindwurmstraße 88')
const postcode = ref('80337')
const city = ref('München')
const country = ref('de')
const sameAsBilling = ref(true)
</script>

<template>
  <div class="mx-auto w-full max-w-md">
    <ShippingAddress
      v-model:first-name="firstName"
      v-model:last-name="lastName"
      v-model:street="street"
      v-model:postcode="postcode"
      v-model:city="city"
      v-model:country="country"
      v-model:same-as-billing="sameAsBilling"
      :countries="countries"
      description="Where should we send your order?"
      step="Step 2 of 4"
      country-hint="Free shipping and returns on all orders."
      continue-label="Continue to delivery"
    />
  </div>
</template>
```

## API reference

### ShippingAddress

Lets the shopper enter a shipping address and continue to the next checkout step.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `countries` (required) | `CountryOption[]` |  | The countries the shopper can ship to, shown in the country select. |
| `title` | `string` | `'Shipping address'` | Shown as the card's title. |
| `description` | `string` |  | Shown under the title. |
| `step` | `string` |  | Where the address sits in the checkout, e.g. `Step 2 of 4`. |
| `firstNameLabel` | `string` | `'First name'` | Shown above the first name field. |
| `lastNameLabel` | `string` | `'Last name'` | Shown above the last name field. |
| `streetLabel` | `string` | `'Street and number'` | Shown above the street field. |
| `postcodeLabel` | `string` | `'Postcode'` | Shown above the postcode field. |
| `cityLabel` | `string` | `'City'` | Shown above the city field. |
| `countryLabel` | `string` | `'Country'` | Shown above the country select. |
| `countryHint` | `string` |  | Shown under the country select, e.g. which countries you ship to. |
| `sameAsBillingLabel` | `string` | `'Billing address is the same'` | Shown beside the checkbox that uses this address for billing too. |
| `continueLabel` | `string` | `'Continue'` | Shown on the button that submits the address. |
| `surface` | `'raised' \| 'bare'` | `'raised'` | Bare drops the edge, shadow and inset, for a section placed inside another Card. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `firstName` | `string` | `''` | The first name the shopper entered. |
| `lastName` | `string` | `''` | The last name the shopper entered. |
| `street` | `string` | `''` | The street and house number the shopper entered. |
| `postcode` | `string` | `''` | The postcode the shopper entered. |
| `city` | `string` | `''` | The city the shopper entered. |
| `country` | `string` | `''` | The selected country, by value. |
| `sameAsBilling` | `boolean` | `true` | Whether the billing address is the same as this one. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:firstName` | `value: string` | Fires with the new value when `firstName` changes; `v-model:firstName` binds it. |
| `update:lastName` | `value: string` | Fires with the new value when `lastName` changes; `v-model:lastName` binds it. |
| `update:street` | `value: string` | Fires with the new value when `street` changes; `v-model:street` binds it. |
| `update:postcode` | `value: string` | Fires with the new value when `postcode` changes; `v-model:postcode` binds it. |
| `update:city` | `value: string` | Fires with the new value when `city` changes; `v-model:city` binds it. |
| `update:country` | `value: string` | Fires with the new value when `country` changes; `v-model:country` binds it. |
| `update:sameAsBilling` | `value: boolean` | Fires with the new value when `sameAsBilling` changes; `v-model:sameAsBilling` binds it. |
| `continue` |  | Fires when the shopper submits the address form to continue. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-surface` | `'raised' \| 'bare'` | Carries the `surface` prop. |

## Exports

```ts
import { type CountryOption } from '@/components/sections/shipping-address'
```

- `CountryOption`: below

```ts
interface CountryOption {
  value: string
  label: string
}
```

## Source

`components/sections/shipping-address/ShippingAddress.vue`

```vue
<script lang="ts">
/** Lets the shopper enter a shipping address and continue to the next checkout step. */
export default {}
</script>

<script setup lang="ts">
import { ArrowRightIcon } from '@lucide/vue'

import type { HTMLAttributes } from 'vue'
import type { CountryOption } from './types'
import { useId } from 'vue'
import { Badge } from '@library/components/ui/badge'
import { Button } from '@library/components/ui/button'
import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@library/components/ui/card'
import { Checkbox } from '@library/components/ui/checkbox'
import { Field, FieldDescription, FieldGroup, FieldLabel } from '@library/components/ui/field'
import { Input } from '@library/components/ui/input'
import { NativeSelect, NativeSelectOption } from '@library/components/ui/native-select'

const props = withDefaults(
  defineProps<{
    /** The countries the shopper can ship to, shown in the country select. */
    countries: CountryOption[]
    /** Shown as the card's title. */
    title?: string
    /** Shown under the title. */
    description?: string
    /** Where the address sits in the checkout, e.g. `Step 2 of 4`. */
    step?: string
    /** Shown above the first name field. */
    firstNameLabel?: string
    /** Shown above the last name field. */
    lastNameLabel?: string
    /** Shown above the street field. */
    streetLabel?: string
    /** Shown above the postcode field. */
    postcodeLabel?: string
    /** Shown above the city field. */
    cityLabel?: string
    /** Shown above the country select. */
    countryLabel?: string
    /** Shown under the country select, e.g. which countries you ship to. */
    countryHint?: string
    /** Shown beside the checkbox that uses this address for billing too. */
    sameAsBillingLabel?: string
    /** Shown on the button that submits the address. */
    continueLabel?: string
    /** Bare drops the edge, shadow and inset, for a section placed inside another Card. */
    surface?: 'raised' | 'bare'
    class?: HTMLAttributes['class']
  }>(),
  {
    surface: 'raised',
    title: 'Shipping address',
    firstNameLabel: 'First name',
    lastNameLabel: 'Last name',
    streetLabel: 'Street and number',
    postcodeLabel: 'Postcode',
    cityLabel: 'City',
    countryLabel: 'Country',
    sameAsBillingLabel: 'Billing address is the same',
    continueLabel: 'Continue',
  },
)
const emit = defineEmits<{
  /** Fires when the shopper submits the address form to continue. */
  continue: []
}>()
/** The first name the shopper entered. */
const firstName = defineModel<string>('firstName', { default: '' })
/** The last name the shopper entered. */
const lastName = defineModel<string>('lastName', { default: '' })
/** The street and house number the shopper entered. */
const street = defineModel<string>('street', { default: '' })
/** The postcode the shopper entered. */
const postcode = defineModel<string>('postcode', { default: '' })
/** The city the shopper entered. */
const city = defineModel<string>('city', { default: '' })
/** The selected country, by value. */
const country = defineModel<string>('country', { default: '' })
/** Whether the billing address is the same as this one. */
const sameAsBilling = defineModel<boolean>('sameAsBilling', { default: true })

const id = useId()
</script>

<template>
  <Card :surface="surface" :class="props.class">
    <CardHeader>
      <CardTitle>{{ title }}</CardTitle>
      <CardDescription v-if="description">{{ description }}</CardDescription>
      <CardAction v-if="step">
        <Badge variant="primary" size="sm">{{ step }}</Badge>
      </CardAction>
    </CardHeader>
    <CardContent>
      <form :id="`${id}-form`" @submit.prevent="emit('continue')">
        <FieldGroup>
          <FieldGroup>
            <div class="grid grid-cols-2 gap-4">
              <Field>
                <FieldLabel :for="`${id}-first-name`">{{ firstNameLabel }}</FieldLabel>
                <Input :id="`${id}-first-name`" v-model="firstName" autocomplete="given-name" />
              </Field>
              <Field>
                <FieldLabel :for="`${id}-last-name`">{{ lastNameLabel }}</FieldLabel>
                <Input :id="`${id}-last-name`" v-model="lastName" autocomplete="family-name" />
              </Field>
            </div>
            <Field>
              <FieldLabel :for="`${id}-street`">{{ streetLabel }}</FieldLabel>
              <Input :id="`${id}-street`" v-model="street" autocomplete="address-line1" />
            </Field>
            <div class="grid grid-cols-3 gap-4">
              <Field>
                <FieldLabel :for="`${id}-postcode`">{{ postcodeLabel }}</FieldLabel>
                <Input :id="`${id}-postcode`" v-model="postcode" autocomplete="postal-code" />
              </Field>
              <Field class="col-span-2">
                <FieldLabel :for="`${id}-city`">{{ cityLabel }}</FieldLabel>
                <Input :id="`${id}-city`" v-model="city" autocomplete="address-level2" />
              </Field>
            </div>
            <Field>
              <FieldLabel :for="`${id}-country`">{{ countryLabel }}</FieldLabel>
              <NativeSelect :id="`${id}-country`" v-model="country" autocomplete="country" class="w-full">
                <NativeSelectOption v-for="option in countries" :key="option.value" :value="option.value">{{ option.label }}</NativeSelectOption>
              </NativeSelect>
              <FieldDescription v-if="countryHint">{{ countryHint }}</FieldDescription>
            </Field>
          </FieldGroup>
          <Field orientation="horizontal">
            <Checkbox :id="`${id}-billing`" v-model="sameAsBilling" />
            <FieldLabel :for="`${id}-billing`">{{ sameAsBillingLabel }}</FieldLabel>
          </Field>
        </FieldGroup>
      </form>
    </CardContent>
    <CardFooter>
      <Button type="submit" :form="`${id}-form`" color="checkout" class="w-full">
        {{ continueLabel }}
        <ArrowRightIcon class="rtl:-scale-x-100" data-icon />
      </Button>
    </CardFooter>
  </Card>
</template>
```

`components/sections/shipping-address/types.ts`

```ts
export interface CountryOption {
  value: string
  label: string
}
```
