- Card
- Inputs
- useSnackbarAction
- React & Vue
Playground
Try a real Bridge UI form.
A personal information CRUD built with Card, inputs, and useSnackbarAction. Switch React / Vue in the header — open the Code tab to inspect the source.
Live preview
Fill the form and save to open a success snackbar. Toggle Preview and Code to move between the running app and its source.
<script setup lang="ts">
// ** External Imports
import { Calendar } from "@lucide/vue";
import { reactive } from "vue";
// ** Bridge UI Imports
import { useSnackbarAction } from "@bridge-ui/vue/Actions";
import { Button } from "@bridge-ui/vue/Components/Button";
import { Card } from "@bridge-ui/vue/Components/Card";
import { Select } from "@bridge-ui/vue/Components/Select";
import { Switch } from "@bridge-ui/vue/Components/Switch";
import { TextField } from "@bridge-ui/vue/Components/TextField";
const countries = [
{ value: "es", label: "Spain" },
{ value: "br", label: "Brazil" },
{ value: "fr", label: "France" },
{ value: "pt", label: "Portugal" },
{ value: "us", label: "United States" },
];
const emptyForm = () => ({
city: "",
email: "",
phone: "",
state: "",
street: "",
lastName: "",
firstName: "",
birthdate: "",
postalCode: "",
acceptTerms: false,
country: null as null | string,
});
const form = reactive(emptyForm());
const snackbar = useSnackbarAction();
const onReset = () => {
Object.assign(form, emptyForm());
};
const onSubmit = (event: Event) => {
event.preventDefault();
onReset();
snackbar.open({
title: "Saved",
color: "success",
description: "Personal information updated successfully.",
});
};
</script>
<template>
<form class="w-full" v-on:reset="onReset" v-on:submit="onSubmit">
<Card title="Personal Information">
<div class="flex flex-col">
<div class="grid gap-4 sm:grid-cols-2">
<TextField
required
name="firstName"
label="First Name"
v-model="form.firstName"
placeholder="First Name"
autocomplete="given-name"
/>
<TextField
required
name="lastName"
label="Last Name"
v-model="form.lastName"
placeholder="Last Name"
autocomplete="family-name"
/>
</div>
<div class="grid gap-4 sm:grid-cols-5">
<TextField
required
type="email"
name="email"
label="Email"
v-model="form.email"
autocomplete="email"
class="sm:col-span-3"
placeholder="[email protected]"
/>
<TextField
type="tel"
name="phone"
label="Phone"
autocomplete="tel"
placeholder="Phone"
v-model="form.phone"
class="sm:col-span-2"
/>
</div>
<div class="grid gap-4 sm:grid-cols-2">
<Select
required
name="country"
label="Country"
:options="countries"
placeholder="Country"
v-model="form.country"
/>
<TextField
type="date"
name="birthdate"
label="Birthdate"
:end-icon="Calendar"
placeholder="Birthdate"
v-model="form.birthdate"
/>
</div>
<TextField
name="street"
v-model="form.street"
label="Street Address"
placeholder="Street Address"
autocomplete="street-address"
/>
<div class="grid gap-4 sm:grid-cols-3">
<TextField
name="city"
label="City"
placeholder="City"
v-model="form.city"
autocomplete="address-level2"
/>
<TextField
name="state"
label="State"
placeholder="State"
v-model="form.state"
autocomplete="address-level1"
/>
<TextField
name="postalCode"
label="Postal Code"
v-model="form.postalCode"
placeholder="Postal Code"
autocomplete="postal-code"
/>
</div>
<Switch
required
name="acceptTerms"
v-model="form.acceptTerms"
end-label="Accept the terms and conditions"
/>
</div>
<template #footer>
<div class="flex justify-end gap-2">
<Button type="reset" variant="flat">Cancel</Button>
<Button type="submit" color="primary">Save</Button>
</div>
</template>
</Card>
</form>
</template>
// ** External Imports
import { Calendar } from "lucide-react";
import { useState, type SubmitEvent } from "react";
// ** Bridge UI Imports
import type { SelectModel } from "@bridge-ui/core";
import { useSnackbarAction } from "@bridge-ui/react/Actions";
import { Button } from "@bridge-ui/react/Components/Button";
import { Card } from "@bridge-ui/react/Components/Card";
import { Select } from "@bridge-ui/react/Components/Select";
import { Switch } from "@bridge-ui/react/Components/Switch";
import { TextField } from "@bridge-ui/react/Components/TextField";
const countries = [
{ value: "es", label: "Spain" },
{ value: "br", label: "Brazil" },
{ value: "fr", label: "France" },
{ value: "pt", label: "Portugal" },
{ value: "us", label: "United States" },
];
const emptyForm = {
city: "",
email: "",
phone: "",
state: "",
street: "",
lastName: "",
firstName: "",
birthdate: "",
postalCode: "",
acceptTerms: false,
country: null as null | SelectModel,
};
export default function FormApp() {
const snackbar = useSnackbarAction();
const [form, setForm] = useState(emptyForm);
const setField = (patch: Partial<typeof emptyForm>) => {
setForm((current) => ({ ...current, ...patch }));
};
const onReset = () => {
setForm(emptyForm);
};
const onSubmit = (event: SubmitEvent<HTMLFormElement>) => {
event.preventDefault();
onReset();
snackbar.open({
title: "Saved",
color: "success",
description: "Personal information updated successfully.",
});
};
return (
<form onReset={onReset} className="w-full" onSubmit={onSubmit}>
<Card
title="Personal Information"
slots={{
footer: (
<div className="flex justify-end gap-2">
<Button type="reset" variant="flat">
Cancel
</Button>
<Button type="submit" color="primary">
Save
</Button>
</div>
),
}}
>
<div className="flex flex-col">
<div className="grid gap-4 sm:grid-cols-2">
<TextField
required
name="firstName"
label="First Name"
value={form.firstName}
placeholder="First Name"
autoComplete="given-name"
onChange={(event) => setField({ firstName: event.target.value })}
/>
<TextField
required
name="lastName"
label="Last Name"
value={form.lastName}
placeholder="Last Name"
autoComplete="family-name"
onChange={(event) => setField({ lastName: event.target.value })}
/>
</div>
<div className="grid gap-4 sm:grid-cols-5">
<TextField
required
type="email"
name="email"
label="Email"
value={form.email}
autoComplete="email"
className="sm:col-span-3"
placeholder="[email protected]"
onChange={(event) => setField({ email: event.target.value })}
/>
<TextField
type="tel"
name="phone"
label="Phone"
autoComplete="tel"
value={form.phone}
placeholder="Phone"
className="sm:col-span-2"
onChange={(event) => setField({ phone: event.target.value })}
/>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<Select
required
name="country"
label="Country"
options={countries}
value={form.country}
placeholder="Country"
onChange={(country) => setField({ country })}
/>
<TextField
type="date"
name="birthdate"
label="Birthdate"
endIcon={Calendar}
value={form.birthdate}
placeholder="Birthdate"
onChange={(event) => setField({ birthdate: event.target.value })}
/>
</div>
<TextField
name="street"
value={form.street}
label="Street Address"
placeholder="Street Address"
autoComplete="street-address"
onChange={(event) => setField({ street: event.target.value })}
/>
<div className="grid gap-4 sm:grid-cols-3">
<TextField
name="city"
label="City"
value={form.city}
placeholder="City"
autoComplete="address-level2"
onChange={(event) => setField({ city: event.target.value })}
/>
<TextField
name="state"
label="State"
value={form.state}
placeholder="State"
autoComplete="address-level1"
onChange={(event) => setField({ state: event.target.value })}
/>
<TextField
name="postalCode"
label="Postal Code"
value={form.postalCode}
placeholder="Postal Code"
autoComplete="postal-code"
onChange={(event) => setField({ postalCode: event.target.value })}
/>
</div>
<Switch
required
name="acceptTerms"
checked={form.acceptTerms}
endLabel="Accept the terms and conditions"
onChange={(event) =>
setField({ acceptTerms: event.target.checked })
}
/>
</div>
</Card>
</form>
);
}
Host setup
The preview mounts the form inside a FormHost that provides BridgeUIProvider and BridgeUIHosts so useSnackbarAction can open snackbars.
VueFormHost.vue
<script setup lang="ts">
// ** Bridge UI Imports
import { BridgeUIProvider } from "@bridge-ui/vue";
import { BridgeUIHosts } from "@bridge-ui/vue/Actions";
// ** Local Imports
import FormApp from "./FormApp.vue";
</script>
<template>
<BridgeUIProvider :global="{}" :components="{}">
<BridgeUIHosts
:snackbar="{
max: 3,
timeout: 5000,
position: 'bottom-center',
}"
>
<FormApp />
</BridgeUIHosts>
</BridgeUIProvider>
</template>
ReactFormHost.tsx
// ** Bridge UI Imports
import { BridgeUIProvider } from "@bridge-ui/react";
import { BridgeUIHosts } from "@bridge-ui/react/Actions";
// ** Local Imports
import FormApp from "./FormApp";
export default function FormHost() {
return (
<BridgeUIProvider global={{}} components={{}}>
<BridgeUIHosts
snackbar={{
max: 3,
timeout: 5000,
position: "bottom-center",
}}
>
<FormApp />
</BridgeUIHosts>
</BridgeUIProvider>
);
}