diff --git a/pkgs/clan-app/ui/src/components/v2/Form/Checkbox.stories.tsx b/pkgs/clan-app/ui/src/components/v2/Form/Checkbox.stories.tsx
new file mode 100644
index 000000000..4685a5e29
--- /dev/null
+++ b/pkgs/clan-app/ui/src/components/v2/Form/Checkbox.stories.tsx
@@ -0,0 +1,68 @@
+import meta, { Story } from "./TextField.stories";
+
+const checkboxMeta = {
+ ...meta,
+ title: "Components/Form/Checkbox",
+};
+
+export default checkboxMeta;
+
+export const Bare: Story = {
+ args: {
+ type: "checkbox",
+ },
+};
+
+export const Label: Story = {
+ args: {
+ ...Bare.args,
+ label: "Accept Terms",
+ },
+};
+
+export const Description: Story = {
+ args: {
+ ...Label.args,
+ description: "That stuff you never bother reading",
+ },
+};
+
+export const Required: Story = {
+ args: {
+ ...Description.args,
+ required: true,
+ },
+};
+
+export const Tooltip: Story = {
+ args: {
+ ...Required.args,
+ tooltip:
+ "Let people know how you got here, great achievements or obstacles overcome",
+ },
+};
+
+export const Invalid: Story = {
+ args: {
+ ...Tooltip.args,
+ invalid: true,
+ },
+};
+
+export const Disabled: Story = {
+ args: {
+ ...Tooltip.args,
+ disabled: true,
+ },
+};
+
+export const ReadOnly: Story = {
+ args: {
+ ...Tooltip.args,
+ readOnly: true,
+ checkbox: {
+ ...Tooltip.args.checkbox,
+ defaultChecked: true,
+ },
+ },
+};
diff --git a/pkgs/clan-app/ui/src/components/v2/Form/Field.css b/pkgs/clan-app/ui/src/components/v2/Form/Field.css
new file mode 100644
index 000000000..d108d005f
--- /dev/null
+++ b/pkgs/clan-app/ui/src/components/v2/Form/Field.css
@@ -0,0 +1,212 @@
+div.form-field {
+ @apply flex items-center w-full;
+
+ & > div.meta {
+ @apply flex flex-col gap-1 w-full;
+
+ & > label,
+ & > div {
+ @apply w-full;
+ /* remove line height which messes with sizing */
+ @apply leading-none;
+ }
+
+ & > label {
+ @apply flex items-center gap-1;
+ }
+
+ & > label[data-required] {
+ span.typography::after {
+ @apply fg-def-4 ml-1;
+
+ content: "*";
+ font-family: "Commit Mono", monospace;
+ font-size: 0.6875rem;
+ }
+ }
+
+ .tooltip-trigger {
+ }
+ }
+
+ & input,
+ & textarea {
+ @apply w-full px-2 py-1.5 rounded-sm;
+ @apply outline outline-1 outline-def-acc-1 bg-def-1 fg-def-1;
+
+ font-weight: 500;
+ font-family: "Archivo", sans-serif;
+ line-height: 132%;
+
+ &::placeholder {
+ @apply fg-def-4;
+ }
+
+ &:hover {
+ @apply bg-def-acc-1 outline-def-acc-2;
+ }
+
+ &:focus-visible {
+ @apply bg-def-1 outline-def-acc-3;
+
+ box-shadow:
+ 0 0 0 0.125rem theme(colors.bg.def.1),
+ 0 0 0 0.1875rem theme(colors.border.semantic.info.1);
+ }
+
+ &[data-invalid] {
+ @apply outline-semantic-error-4;
+ }
+
+ &[data-disabled] {
+ @apply outline-def-2 fg-def-4 cursor-not-allowed;
+ }
+
+ &[data-readonly] {
+ @apply outline-def-2 cursor-not-allowed;
+ }
+ }
+
+ &.orientation-vertical {
+ @apply flex-col gap-2;
+ }
+
+ &.orientation-horizontal {
+ @apply flex-row gap-2 justify-between;
+
+ & > div.meta {
+ @apply w-1/2 shrink;
+ }
+
+ & div.input-container,
+ & textarea {
+ @apply w-1/2 grow;
+ }
+
+ &:has(> textarea) {
+ @apply items-start;
+ }
+ }
+
+ div.input-container {
+ @apply inline-block relative w-full;
+
+ /* I'm unsure why I have to do this */
+ @apply leading-none;
+
+ & > input {
+ @apply w-full;
+
+ &.has-icon {
+ @apply pl-7;
+ }
+ }
+
+ & > .icon {
+ @apply absolute left-2 top-1/2 transform -translate-y-1/2;
+ @apply w-[0.875rem] h-[0.875rem] pointer-events-none;
+ }
+ }
+
+ &.form-field-checkbox {
+ @apply items-start;
+
+ & > div.checkbox-control {
+ @apply w-5 h-5 rounded-sm bg-def-1 border border-inv-1 p-[0.0625rem];
+
+ &[data-disabled] {
+ @apply border-def-2;
+ }
+
+ &[data-invalid] {
+ @apply border-semantic-error-4;
+ }
+ }
+ }
+
+ &.size-default {
+ & input,
+ & textarea {
+ font-size: 0.875rem;
+ }
+
+ div.input-container {
+ @apply h-[1.875rem];
+
+ input {
+ @apply h-[1.875rem];
+ }
+ }
+ }
+
+ &.size-s {
+ & input,
+ & textarea {
+ @apply px-1.5 py-1;
+ font-size: 0.75rem;
+ }
+
+ div.input-container {
+ @apply h-[1.25rem];
+
+ input {
+ @apply h-[1.25rem];
+ }
+
+ input.has-icon {
+ @apply pl-6;
+ }
+
+ & > .icon {
+ @apply w-[0.6875rem] h-[0.6875rem] transform -translate-y-1/2;
+ }
+ }
+ }
+
+ &.inverted {
+ & input,
+ & textarea {
+ @apply bg-inv-1 fg-inv-1 outline-inv-acc-1;
+
+ &::placeholder {
+ @apply fg-inv-4;
+ }
+
+ &:hover {
+ @apply bg-inv-acc-2 outline-inv-acc-2;
+ }
+
+ &:focus-visible {
+ @apply bg-inv-acc-4;
+ box-shadow:
+ 0 0 0 0.125rem theme(colors.bg.inv.1),
+ 0 0 0 0.1875rem theme(colors.border.semantic.info.1);
+ }
+
+ &[data-invalid] {
+ @apply outline-semantic-error-4;
+ }
+ }
+
+ &.form-field-checkbox {
+ & > div.checkbox-control {
+ @apply bg-inv-acc-4;
+
+ &[data-disabled] {
+ @apply bg-def-4 border-none;
+ }
+ }
+ }
+ }
+
+ &.ghost {
+ & input,
+ & textarea {
+ @apply outline-none;
+
+ &:hover {
+ @apply outline-none;
+ }
+ }
+ }
+}
diff --git a/pkgs/clan-app/ui/src/components/v2/Form/Field.tsx b/pkgs/clan-app/ui/src/components/v2/Form/Field.tsx
new file mode 100644
index 000000000..2e762ee23
--- /dev/null
+++ b/pkgs/clan-app/ui/src/components/v2/Form/Field.tsx
@@ -0,0 +1,214 @@
+import {
+ TextField as KTextField,
+ TextFieldInputProps as KTextFieldInputProps,
+ TextFieldTextAreaProps as KTextFieldTextAreaProps,
+} from "@kobalte/core/text-field";
+import {
+ Checkbox as KCheckbox,
+ CheckboxInputProps as KCheckboxInputProps,
+} from "@kobalte/core/checkbox";
+import { Typography } from "@/src/components/v2/Typography/Typography";
+import Icon, { IconVariant } from "@/src/components/v2/Icon/Icon";
+
+import cx from "classnames";
+import { Match, splitProps, Switch } from "solid-js";
+import { Dynamic } from "solid-js/web";
+import { Tooltip as KTooltip } from "@kobalte/core/tooltip";
+import "./Field.css";
+
+type Size = "default" | "s";
+type Orientation = "horizontal" | "vertical";
+type FieldType = "text" | "textarea" | "checkbox";
+
+export interface TextFieldProps {
+ input?: KTextFieldInputProps;
+ value?: string;
+ onChange?: (value: string) => void;
+ defaultValue?: string;
+}
+
+export interface TextAreaProps {
+ input?: KTextFieldTextAreaProps;
+ value?: string;
+ onChange?: (value: string) => void;
+ defaultValue?: string;
+}
+
+export interface CheckboxProps {
+ input?: KCheckboxInputProps;
+ checked?: boolean;
+ defaultChecked?: boolean;
+ indeterminate?: boolean;
+ onChange?: (value: boolean) => void;
+}
+
+export interface FieldProps {
+ class?: string;
+ name?: string;
+ label?: string;
+ description?: string;
+ tooltip?: string;
+ icon?: IconVariant;
+ ghost?: boolean;
+
+ size?: Size;
+ orientation?: Orientation;
+ inverted?: boolean;
+
+ required?: boolean;
+ disabled?: boolean;
+ readOnly?: boolean;
+ invalid?: boolean;
+
+ type: FieldType;
+ text?: TextFieldProps;
+ textarea?: TextAreaProps;
+ checkbox?: CheckboxProps;
+}
+
+const componentsForType = {
+ text: {
+ container: KTextField,
+ label: KTextField.Label,
+ description: KTextField.Description,
+ },
+ textarea: {
+ container: KTextField,
+ label: KTextField.Label,
+ description: KTextField.Description,
+ },
+ checkbox: {
+ container: KCheckbox,
+ label: KCheckbox.Label,
+ description: KCheckbox.Description,
+ },
+};
+
+export const Field = (props: FieldProps) => {
+ const [commonProps] = splitProps(props, [
+ "name",
+ "class",
+ "required",
+ "disabled",
+ "readOnly",
+ ]);
+
+ const [textProps] = splitProps(props.text || props.textarea || {}, [
+ "value",
+ "onChange",
+ "defaultValue",
+ ]);
+
+ const [checkboxProps] = splitProps(props.checkbox || {}, [
+ "checked",
+ "defaultChecked",
+ "indeterminate",
+ ]);
+
+ const validationState = () => (props.invalid ? "invalid" : "valid");
+
+ const labelSize = () => `size-${props.size || "default"}`;
+ const orientation = () => `orientation-${props.orientation || "vertical"}`;
+ const descriptionSize = () => (labelSize() == "size-default" ? "xs" : "xxs");
+ const fieldClass = () => (props.type ? `form-field-${props.type}` : "");
+
+ const { container, label, description } = componentsForType[props.type];
+
+ return (
+