diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md
index bba5a6c46e..058250c599 100644
--- a/packages/canon/report.api.md
+++ b/packages/canon/report.api.md
@@ -6,8 +6,8 @@
///
import type { CSSProperties } from 'react';
-import { Field } from '@base-ui-components/react/field';
import { ForwardRefExoticComponent } from 'react';
+import { Input as Input_2 } from '@base-ui-components/react/input';
import { default as React_2 } from 'react';
import * as React_3 from 'react';
import { ReactNode } from 'react';
@@ -326,26 +326,11 @@ export interface InlineProps extends SpaceProps {
// @public (undocumented)
export const Input: React_2.ForwardRefExoticComponent<
- InputProps & React_2.RefAttributes
+ InputProps & React_2.RefAttributes
>;
// @public (undocumented)
-export interface InputProps
- extends React.ComponentPropsWithoutRef {
- // (undocumented)
- description?: string;
- // (undocumented)
- errorForceShow?: boolean;
- // (undocumented)
- errorMatch?: Field.Error.Props['match'];
- // (undocumented)
- errorMessage?: string;
- // (undocumented)
- label?: string;
- // (undocumented)
- placeholder?: string;
- // (undocumented)
- required?: boolean;
+export interface InputProps extends Omit {
// (undocumented)
size?: 'sm' | 'md';
}
diff --git a/packages/canon/src/components/Field/Field.stories.tsx b/packages/canon/src/components/Field/Field.stories.tsx
new file mode 100644
index 0000000000..911bb07245
--- /dev/null
+++ b/packages/canon/src/components/Field/Field.stories.tsx
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { Field } from './Field';
+import { Input } from '../Input/Input';
+
+const meta = {
+ title: 'Components/Field',
+ component: Field.Root,
+} satisfies Meta;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => (
+
+ Label
+
+ Error
+
+ ),
+};
+
+export const WithLabelAndDescription: Story = {
+ render: () => (
+
+ Label
+
+ Description
+
+ ),
+};
+
+export const WithError: Story = {
+ render: () => (
+
+ value !== 'Backstage' ? 'Please enter a different name' : null
+ }
+ validationMode="onChange"
+ >
+ Name
+
+
+ An error will show if the value is not Backstage
+
+ Error
+
+ ),
+};
diff --git a/packages/canon/src/components/Field/Field.styles.css b/packages/canon/src/components/Field/Field.styles.css
new file mode 100644
index 0000000000..485cf32c15
--- /dev/null
+++ b/packages/canon/src/components/Field/Field.styles.css
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.canon-FieldRoot {
+ display: flex;
+ flex-direction: column;
+ font-family: var(--canon-font-regular);
+ width: 100%;
+}
+
+.canon-FieldLabel {
+ font-size: var(--canon-font-size-caption);
+ font-weight: var(--canon-font-weight-regular);
+ color: var(--canon-text-primary);
+ margin-bottom: var(--canon-spacing-3xs);
+}
+
+.canon-FieldDescription {
+ font-size: var(--canon-font-size-caption);
+ font-weight: var(--canon-font-weight-regular);
+ color: var(--canon-text-secondary);
+ margin: 0;
+ padding-top: var(--canon-spacing-3xs);
+}
+
+.canon-FieldError {
+ font-size: var(--canon-font-size-caption);
+ font-weight: var(--canon-font-weight-regular);
+ color: var(--canon-error);
+ margin: 0;
+ padding-top: var(--canon-spacing-3xs);
+}
diff --git a/packages/canon/src/components/Field/Field.tsx b/packages/canon/src/components/Field/Field.tsx
new file mode 100644
index 0000000000..2e512e7944
--- /dev/null
+++ b/packages/canon/src/components/Field/Field.tsx
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Field as FieldPrimitive } from '@base-ui-components/react/field';
+import clsx from 'clsx';
+
+const FieldRoot = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+FieldRoot.displayName = FieldPrimitive.Root.displayName;
+
+const FieldLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+FieldLabel.displayName = FieldPrimitive.Label.displayName;
+
+const FieldDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+FieldDescription.displayName = FieldPrimitive.Description.displayName;
+
+const FieldError = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+FieldError.displayName = FieldPrimitive.Error.displayName;
+
+const FieldValidity = ({
+ children,
+ ...props
+}: React.ComponentPropsWithoutRef) => (
+
+ {validityState => children(validityState)}
+
+);
+
+export const Field = {
+ Root: FieldRoot,
+ Label: FieldLabel,
+ Description: FieldDescription,
+ Error: FieldError,
+ Validity: FieldValidity,
+};
diff --git a/packages/canon/src/components/Field/index.ts b/packages/canon/src/components/Field/index.ts
new file mode 100644
index 0000000000..297ed9b27b
--- /dev/null
+++ b/packages/canon/src/components/Field/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './Field';
diff --git a/packages/canon/src/components/Input/Input.stories.tsx b/packages/canon/src/components/Input/Input.stories.tsx
index a3f2f59581..a215162688 100644
--- a/packages/canon/src/components/Input/Input.stories.tsx
+++ b/packages/canon/src/components/Input/Input.stories.tsx
@@ -22,93 +22,20 @@ import { Inline } from '../Inline';
const meta = {
title: 'Components/Input',
component: Input,
- decorators: [
- Story => (
-