Checkout
Checkout page in one of the most important pages in e-commerce. Usually it contains information about delivery destination, shipping options and payment methods.
Address form
Ease the checkout process for users by providing them with well-structured address form. Form field Street
provides you an example of how error state could be handled.
<template>
<form class="p-4 flex gap-4 flex-wrap text-neutral-900" @submit.prevent="onSubmit">
<h2 class="w-full typography-headline-4 md:typography-headline-3 font-bold">Billing address</h2>
<label class="w-full md:w-auto flex-grow flex flex-col gap-0.5 mt-4 md:mt-0">
<span class="typography-text-sm font-medium">First Name</span>
<SfInput name="firstName" autocomplete="given-name" required />
</label>
<label class="w-full md:w-auto flex-grow flex flex-col gap-0.5">
<span class="typography-text-sm font-medium">Last Name</span>
<SfInput name="lastName" autocomplete="family-name" required />
</label>
<label class="w-full flex flex-col gap-0.5">
<span class="typography-text-sm font-medium">Phone</span>
<SfInput name="phone" type="tel" autocomplete="tel" required />
</label>
<label class="w-full flex flex-col gap-0.5 flex flex-col gap-0.5">
<span class="typography-text-sm font-medium">Country</span>
<SfSelect name="country" placeholder="-- Select --" autocomplete="country-name" required>
<option v-for="countryName in countries" :key="countryName">{{ countryName }}</option>
</SfSelect>
</label>
<div class="w-full md:w-auto flex-grow flex flex-col gap-0.5">
<label>
<span class="typography-text-sm font-medium">Street</span>
<SfInput
name="street"
autocomplete="address-line1"
class="mt-0.5"
required
:invalid="!streetIsValid"
@blur="streetIsValid = !!$event.target.value"
@update:model-value="streetIsValid = !!$event"
/>
</label>
<div class="flex flex-colr mt-0.5">
<strong v-if="!streetIsValid" class="typography-error-sm text-negative-700 font-medium">
Please provide a valid street name
</strong>
<small class="typography-hint-xs text-neutral-500">Street address or P.O. Box</small>
</div>
</div>
<div class="w-full flex flex-col gap-0.5 md:w-[120px]">
<label>
<span class="typography-text-sm font-medium">Apt#, Suite, etc</span>
<SfInput name="aptNo" class="mt-0.5" />
</label>
<small class="typography-hint-xs text-neutral-500 mt-0.5">Optional</small>
</div>
<label class="w-full flex flex-col gap-0.5">
<span class="typography-text-sm font-medium">City</span>
<SfInput name="city" autocomplete="address-level2" required />
</label>
<label class="w-full md:w-auto flex flex-col gap-0.5 flex-grow">
<span class="typography-text-sm font-medium">State</span>
<SfSelect name="state" placeholder="-- Select --" autocomplete="address-level1" required>
<option v-for="stateName in states" :key="stateName">{{ stateName }}</option>
</SfSelect>
</label>
<label class="w-full flex flex-col gap-0.5 md:w-[120px]">
<span class="typography-text-sm font-medium">ZIP Code</span>
<SfInput name="zipCode" placeholder="eg. 12345" autocomplete="postal-code" required />
</label>
<label class="w-full flex items-center gap-2">
<SfCheckbox name="useAsShippingAddress" />
Use as shipping address
</label>
<div class="w-full flex gap-4 mt-4 md:mt-0 md:justify-end">
<SfButton type="reset" variant="secondary" class="w-full md:w-auto">Clear all</SfButton>
<SfButton type="submit" class="w-full md:w-auto">Save</SfButton>
</div>
</form>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { SfSelect, SfInput, SfCheckbox, SfButton } from '@storefront-ui/vue';
// Here you should provide a list of countries you want to support
// or use an up-to-date country list like: https://www.npmjs.com/package/country-list
const countries = ['Germany', 'Great Britain', 'Poland', 'United States of America'] as const;
const states = ['California', 'Florida', 'New York', 'Texas'] as const;
const streetIsValid = ref(true);
const onSubmit = (e: Event) => {
/* your submit handler, e.g.: */
const form = e.target as HTMLFormElement;
// data can be accessed in form of FormData
const formData = new FormData(form);
// or JSON object
const formJSON = Object.fromEntries(formData.entries());
console.log(formJSON);
};
</script>
Delivery options
Present possible delivery options in a way where your customers can easily see differences and choose the best one for their needs.
<template>
<SfListItem
v-for="({ name, cost, date }, index) in deliveryOptions"
:key="index"
tag="label"
class="!items-start max-w-sm border rounded-md border-neutral-200 first-of-type:mr-4 first-of-type:mb-4"
>
{{ name }}
<span class="text-xs text-gray-500 break-words">{{ date }}</span>
<template #prefix>
<SfRadio v-model="radioModel" :value="name" name="delivery-options" />
</template>
<template #suffix>
<span class="text-gray-900">{{ cost }}</span>
</template>
</SfListItem>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { SfRadio, SfListItem } from '@storefront-ui/vue';
const deliveryOptions = [
{
name: 'Standard',
cost: 'Free',
date: 'Thursday, September 15',
},
{
name: 'Express',
cost: '$5.00',
date: 'Tomorrow, September 12',
},
];
const radioModel = ref('');
</script>
Payment method
Let your users pick a payment method of their choice in a nice and clear way.
<template>
<fieldset>
<legend class="mb-4 typography-headline-5 font-bold text-neutral-900">Payment method</legend>
<div class="grid grid-cols-2 gap-4 items-stretch">
<label v-for="{ label, value, logo, active } in paymentMethods" :key="value" class="relative">
<input type="radio" name="payment_method" class="peer sr-only" :value="value" :disabled="!active" />
<div
class="h-full flex flex-col items-center justify-center py-7 px-4 cursor-pointer rounded-md border border-neutral-200 -outline-offset-2 hover:border-primary-200 hover:bg-primary-100 peer-focus:border-primary-200 peer-focus:bg-primary-100 active:border-primary-300 active:bg-primary-200 peer-checked:outline peer-checked:outline-2 peer-checked:outline-primary-700 peer-disabled:opacity-50 peer-disabled:bg-neutral-100 peer-disabled:border-neutral-200 peer-disabled:cursor-not-allowed peer-disabled:[&_img]:grayscale"
>
<img :src="logo" :alt="label" class="h-6 select-none" />
<p v-if="!active" class="absolute bottom-2 select-none text-disabled-900 typography-text-xs">Coming soon</p>
</div>
</label>
</div>
</fieldset>
</template>
<script lang="ts" setup>
// List of payment methods
const paymentMethods = [
{
label: 'Credit card',
value: 'credit-card',
logo: 'https://storage.googleapis.com/sfui_docs_artifacts_bucket_public/production/visa-logo.svg',
active: true,
},
{
label: 'PayPal',
value: 'paypal',
logo: 'https://storage.googleapis.com/sfui_docs_artifacts_bucket_public/production/paypal-logo.svg',
active: true,
},
{
label: 'ApplePay',
value: 'applepay',
logo: 'https://storage.googleapis.com/sfui_docs_artifacts_bucket_public/production/apple-pay-logo.svg',
active: true,
},
{
label: 'GooglePay',
value: 'googlepay',
logo: 'https://storage.googleapis.com/sfui_docs_artifacts_bucket_public/production/google-pay-logo.svg',
active: false,
},
];
</script>
Contact form
The contact form for customers provides the way to send email (field with simple validation) and phone number with separate country code.
<template>
<form class="flex flex-col gap-y-4 text-neutral-900" @submit.prevent="onSubmit">
<h3 class="font-bold typography-headline-4 md:typography-headline-3">Contact information</h3>
<div class="gap-y-0.5">
<label class="gap-y-0.5">
<span class="text-sm font-medium">Email</span>
<SfInput
v-model="value"
name="email"
placeholder="email address"
:invalid="invalid"
auto-complete="email"
@input="handleValidation"
/>
</label>
<p v-if="invalid" class="typography-error-sm text-negative-700 font-medium mt-0.5">
Please provide a valid email
</p>
</div>
<label class="flex flex-col gap-y-0.5">
<span class="font-medium typography-text-sm">Phone number</span>
<div class="flex">
<SfSelect name="phone-country-code" class="w-16 mr-4" placeholder="--" auto-complete="tel-country-code">
<option v-for="option in options" :key="option" :value="option">
{{ option }}
</option>
))}
</SfSelect>
<SfInput
name="phone-national"
wrapper-class="w-full"
type="tel"
input-mode="tel"
auto-complete="tel-national"
/>
</div>
</label>
<div class="flex justify-between gap-4 md:justify-end">
<SfButton class="w-full md:w-auto" type="reset" variant="secondary"> Clear all </SfButton>
<SfButton class="w-full md:w-auto" type="submit">Save</SfButton>
</div>
</form>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { SfInput, SfButton, SfSelect } from '@storefront-ui/vue';
const invalid = ref(true);
const options = [1, 7, 20, 27, 30, 30, 31, 32, 33, 34, 36, 39, 40, 41, 43, 44, 45, 46, 47, 48, 49, 51];
const emailRegExp = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/;
const value = ref('');
const handleValidation = (event: InputEvent) => {
invalid.value = !emailRegExp.test((event.target as HTMLInputElement).value);
};
const onSubmit = (e: Event) => {
/* your submit handler, e.g.: */
const form = e.target as HTMLFormElement;
// data can be accessed in form of FormData
const formData = new FormData(form);
// or JSON object
const formJSON = Object.fromEntries(formData.entries());
console.log(formJSON);
};
</script>