diff --git a/.changeset/ui-date-picker.md b/.changeset/ui-date-picker.md
new file mode 100644
index 0000000000..eba3624e99
--- /dev/null
+++ b/.changeset/ui-date-picker.md
@@ -0,0 +1,7 @@
+---
+'@backstage/ui': patch
+---
+
+Added new `DatePicker` component — combines a date field and a calendar popover for selecting a date, built on React Aria with full keyboard and screen reader accessibility. Uses BUI design tokens throughout, including auto-incremented backgrounds via the bg consumer pattern.
+
+**Affected components:** DatePicker
diff --git a/docs-ui/src/app/components/date-picker/components.tsx b/docs-ui/src/app/components/date-picker/components.tsx
new file mode 100644
index 0000000000..699831c793
--- /dev/null
+++ b/docs-ui/src/app/components/date-picker/components.tsx
@@ -0,0 +1,36 @@
+'use client';
+
+import { DatePicker } from '../../../../../packages/ui/src/components/DatePicker/DatePicker';
+import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
+import { parseDate } from '@internationalized/date';
+
+export const WithLabel = () => {
+ return ;
+};
+
+export const Sizes = () => {
+ return (
+
+
+
+
+ );
+};
+
+export const WithDefaultValue = () => {
+ return (
+
+ );
+};
+
+export const Disabled = () => {
+ return ;
+};
diff --git a/docs-ui/src/app/components/date-picker/page.mdx b/docs-ui/src/app/components/date-picker/page.mdx
new file mode 100644
index 0000000000..d0e8cfdc11
--- /dev/null
+++ b/docs-ui/src/app/components/date-picker/page.mdx
@@ -0,0 +1,82 @@
+import { PropsTable } from '@/components/PropsTable';
+import { Snippet } from '@/components/Snippet';
+import { datePickerPropDefs } from './props-definition';
+import {
+ datePickerUsageSnippet,
+ withLabelSnippet,
+ sizesSnippet,
+ withDefaultValueSnippet,
+ disabledSnippet,
+} from './snippets';
+import { WithLabel, Sizes, WithDefaultValue, Disabled } from './components';
+import { PageTitle } from '@/components/PageTitle';
+import { Theming } from '@/components/Theming';
+import { DatePickerDefinition } from '../../../utils/definitions';
+import { ChangelogComponent } from '@/components/ChangelogComponent';
+import { CodeBlock } from '@/components/CodeBlock';
+import { ReactAriaLink } from '@/components/ReactAriaLink';
+
+export const reactAriaUrls = {
+ datePicker: 'https://react-aria.adobe.com/DatePicker',
+};
+
+
+
+}
+ code={withLabelSnippet}
+/>
+
+## Usage
+
+
+
+## API reference
+
+
+
+
+
+## Examples
+
+### Sizes
+
+}
+ code={sizesSnippet}
+ layout="side-by-side"
+/>
+
+### With default value
+
+}
+ code={withDefaultValueSnippet}
+ layout="side-by-side"
+/>
+
+### Disabled
+
+}
+ code={disabledSnippet}
+ layout="side-by-side"
+/>
+
+
+
+
diff --git a/docs-ui/src/app/components/date-picker/props-definition.tsx b/docs-ui/src/app/components/date-picker/props-definition.tsx
new file mode 100644
index 0000000000..ccb5842b17
--- /dev/null
+++ b/docs-ui/src/app/components/date-picker/props-definition.tsx
@@ -0,0 +1,94 @@
+import {
+ classNamePropDefs,
+ stylePropDefs,
+ type PropDef,
+} from '@/utils/propDefs';
+import { Chip } from '@/components/Chip';
+
+export const datePickerPropDefs: Record = {
+ size: {
+ type: 'enum',
+ values: ['small', 'medium'],
+ default: 'small',
+ responsive: true,
+ description: (
+ <>
+ Visual size of the picker. Use small for dense layouts,{' '}
+ medium for prominent fields.
+ >
+ ),
+ },
+ label: {
+ type: 'string',
+ description: 'Visible label displayed above the picker.',
+ },
+ secondaryLabel: {
+ type: 'string',
+ description: (
+ <>
+ Secondary text shown next to the label. If not provided and isRequired
+ is true, displays Required.
+ >
+ ),
+ },
+ description: {
+ type: 'string',
+ description: 'Help text displayed below the label.',
+ },
+ value: {
+ type: 'enum',
+ values: ['DateValue'],
+ description: 'Controlled value of the date.',
+ },
+ defaultValue: {
+ type: 'enum',
+ values: ['DateValue'],
+ description: 'Default value for uncontrolled usage.',
+ },
+ onChange: {
+ type: 'enum',
+ values: ['(value: DateValue | null) => void'],
+ description: 'Handler called when the selected date changes.',
+ },
+ granularity: {
+ type: 'enum',
+ values: ['day', 'hour', 'minute', 'second'],
+ default: 'day',
+ description:
+ 'Smallest unit displayed. Defaults to "day" for dates and "minute" for times.',
+ },
+ minValue: {
+ type: 'enum',
+ values: ['DateValue'],
+ description: 'Minimum allowed date. Dates before this are disabled.',
+ },
+ maxValue: {
+ type: 'enum',
+ values: ['DateValue'],
+ description: 'Maximum allowed date. Dates after this are disabled.',
+ },
+ isDateUnavailable: {
+ type: 'enum',
+ values: ['(date: DateValue) => boolean'],
+ description:
+ 'Callback invoked for each calendar date. Return true to mark a date as unavailable.',
+ },
+ name: {
+ type: 'string',
+ description: 'Form field name for the date, submitted as ISO 8601.',
+ },
+ isRequired: {
+ type: 'boolean',
+ description: 'Whether the field is required for form submission.',
+ },
+ isDisabled: {
+ type: 'boolean',
+ description: 'Whether the picker is disabled.',
+ },
+ isReadOnly: {
+ type: 'boolean',
+ description: 'Whether the picker is read-only.',
+ },
+ ...classNamePropDefs,
+ ...stylePropDefs,
+};
diff --git a/docs-ui/src/app/components/date-picker/snippets.ts b/docs-ui/src/app/components/date-picker/snippets.ts
new file mode 100644
index 0000000000..9bab51bf45
--- /dev/null
+++ b/docs-ui/src/app/components/date-picker/snippets.ts
@@ -0,0 +1,25 @@
+export const datePickerUsageSnippet = `import { DatePicker } from '@backstage/ui';
+
+`;
+
+export const withLabelSnippet = ``;
+
+export const sizesSnippet = `
+
+
+`;
+
+export const withDefaultValueSnippet = `import { parseDate } from '@internationalized/date';
+
+`;
+
+export const disabledSnippet = ``;
diff --git a/docs-ui/src/utils/data.ts b/docs-ui/src/utils/data.ts
index fb7ac148a5..515deacb13 100644
--- a/docs-ui/src/utils/data.ts
+++ b/docs-ui/src/utils/data.ts
@@ -58,6 +58,10 @@ export const components: Page[] = [
title: 'Container',
slug: 'container',
},
+ {
+ title: 'DatePicker',
+ slug: 'date-picker',
+ },
{
title: 'DateRangePicker',
slug: 'date-range-picker',
diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md
index ac6c9f13a2..3fcdb8319e 100644
--- a/packages/ui/report.api.md
+++ b/packages/ui/report.api.md
@@ -15,6 +15,7 @@ import type { ComponentProps } from 'react';
import type { ComponentPropsWithoutRef } from 'react';
import type { ComponentPropsWithRef } from 'react';
import type { CSSProperties } from 'react';
+import type { DatePickerProps as DatePickerProps_2 } from 'react-aria-components';
import type { DateRangePickerProps as DateRangePickerProps_2 } from 'react-aria-components';
import type { DateValue } from '@internationalized/date';
import type { DialogTriggerProps as DialogTriggerProps_2 } from 'react-aria-components';
@@ -1211,6 +1212,79 @@ export interface CursorResponse {
totalCount?: number;
}
+// @public
+export const DatePicker: ForwardRefExoticComponent<
+ DatePickerProps & RefAttributes
+>;
+
+// @public
+export const DatePickerCalendarDefinition: {
+ readonly styles: {
+ readonly [key: string]: string;
+ };
+ readonly classNames: {
+ readonly root: 'bui-DatePickerCalendar';
+ readonly header: 'bui-DatePickerCalendarHeader';
+ readonly heading: 'bui-DatePickerCalendarHeading';
+ readonly navButton: 'bui-DatePickerCalendarNavButton';
+ readonly grid: 'bui-DatePickerCalendarGrid';
+ readonly gridHeader: 'bui-DatePickerCalendarGridHeader';
+ readonly headerCell: 'bui-DatePickerCalendarHeaderCell';
+ readonly gridBody: 'bui-DatePickerCalendarGridBody';
+ readonly cell: 'bui-DatePickerCalendarCell';
+ };
+ readonly propDefs: {};
+};
+
+// @public
+export const DatePickerDefinition: {
+ readonly styles: {
+ readonly [key: string]: string;
+ };
+ readonly classNames: {
+ readonly root: 'bui-DatePicker';
+ };
+ readonly propDefs: {
+ readonly size: {
+ readonly dataAttribute: true;
+ readonly default: 'small';
+ };
+ readonly className: {};
+ readonly label: {};
+ readonly description: {};
+ readonly secondaryLabel: {};
+ };
+};
+
+// @public
+export const DatePickerGroupDefinition: {
+ readonly styles: {
+ readonly [key: string]: string;
+ };
+ readonly classNames: {
+ readonly root: 'bui-DatePickerGroup';
+ readonly dateInput: 'bui-DatePickerDateInput';
+ readonly segment: 'bui-DatePickerSegment';
+ readonly button: 'bui-DatePickerButton';
+ };
+ readonly bg: 'consumer';
+ readonly propDefs: {};
+};
+
+// @public (undocumented)
+export type DatePickerOwnProps = {
+ size?: 'small' | 'medium' | Partial>;
+ className?: string;
+ label?: FieldLabelProps['label'];
+ description?: FieldLabelProps['description'];
+ secondaryLabel?: FieldLabelProps['secondaryLabel'];
+};
+
+// @public (undocumented)
+export interface DatePickerProps
+ extends Omit, 'className' | 'children'>,
+ DatePickerOwnProps {}
+
// @public
export const DateRangePicker: ForwardRefExoticComponent<
DateRangePickerProps & RefAttributes
diff --git a/packages/ui/src/components/DatePicker/DatePicker.module.css b/packages/ui/src/components/DatePicker/DatePicker.module.css
new file mode 100644
index 0000000000..6fd60dffac
--- /dev/null
+++ b/packages/ui/src/components/DatePicker/DatePicker.module.css
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2025 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.
+ */
+
+@layer tokens, base, components, utilities;
+
+@layer components {
+ /* ============================================================
+ Root
+ ============================================================ */
+
+ .bui-DatePicker {
+ display: flex;
+ flex-direction: column;
+ font-family: var(--bui-font-regular);
+ width: 100%;
+ flex-shrink: 0;
+ }
+
+ /* ============================================================
+ Field group — custom container (not reusing any BUI field)
+ ============================================================ */
+
+ .bui-DatePickerGroup {
+ display: flex;
+ align-items: center;
+ background-color: var(--bui-bg-neutral-1);
+ border-radius: var(--bui-radius-2);
+ padding: 0 var(--bui-space-1) 0 var(--bui-space-3);
+ width: fit-content;
+ min-width: 200px;
+ max-width: 100%;
+ overflow: clip;
+ transition: box-shadow 0.2s ease-in-out;
+ cursor: text;
+
+ /* bg consumer — auto-increment background based on parent context */
+ &[data-on-bg='neutral-1'] {
+ background-color: var(--bui-bg-neutral-2);
+ }
+
+ &[data-on-bg='neutral-2'] {
+ background-color: var(--bui-bg-neutral-3);
+ }
+
+ &[data-on-bg='neutral-3'] {
+ background-color: var(--bui-bg-neutral-4);
+ }
+
+ &[data-focus-within] {
+ outline: none;
+ box-shadow: inset 0 0 0 1px var(--bui-ring);
+ }
+
+ &[data-invalid] {
+ box-shadow: inset 0 0 0 1px var(--bui-border-danger);
+ }
+
+ &[data-disabled] {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+
+ /* Sizes */
+ &[data-size='small'] {
+ height: 2rem;
+ padding-inline-start: var(--bui-space-2);
+ }
+
+ &[data-size='medium'] {
+ height: 2.5rem;
+ min-width: 210px;
+ padding-inline-end: var(--bui-space-1);
+
+ & .bui-DatePickerButton {
+ width: 2rem;
+ height: 2rem;
+ }
+ }
+ }
+
+ /* ============================================================
+ Date input
+ ============================================================ */
+
+ .bui-DatePickerDateInput {
+ flex: 1;
+ display: inline-flex;
+ align-items: center;
+ width: unset;
+ min-width: unset;
+ padding: unset;
+ border: unset;
+ box-shadow: none;
+ background: none;
+ height: auto;
+ overflow-x: auto;
+ overflow-y: clip;
+ scrollbar-width: none;
+ }
+
+ /* ============================================================
+ Date segments (month, day, year literals and placeholders)
+ ============================================================ */
+
+ .bui-DatePickerSegment {
+ display: inline-block;
+ padding: var(--bui-space-0_5) var(--bui-space-1);
+ border-radius: var(--bui-radius-1);
+ font-size: var(--bui-font-size-3);
+ font-family: var(--bui-font-regular);
+ font-weight: var(--bui-font-weight-regular);
+ color: var(--bui-fg-primary);
+ caret-color: transparent;
+ outline: none;
+ font-variant-numeric: tabular-nums;
+
+ &[data-placeholder] {
+ color: var(--bui-fg-secondary);
+ }
+
+ &[data-type='literal'] {
+ color: var(--bui-fg-secondary);
+ padding: 0;
+ }
+
+ &[data-focused] {
+ background-color: var(--bui-bg-solid);
+ color: var(--bui-fg-solid);
+ border-radius: var(--bui-radius-1);
+
+ &[data-placeholder] {
+ color: var(--bui-fg-solid);
+ }
+ }
+
+ &[data-disabled] {
+ color: var(--bui-fg-disabled);
+ }
+ }
+
+ /* ============================================================
+ Calendar trigger button
+ ============================================================ */
+
+ .bui-DatePickerButton {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: 1.5rem;
+ height: 1.5rem;
+ padding: 0;
+ background: none;
+ border: none;
+ border-radius: var(--bui-radius-2);
+ color: var(--bui-fg-secondary);
+ cursor: pointer;
+ margin-left: auto;
+ transition: color 0.15s ease-in-out;
+ outline: none;
+
+ &[data-hovered] {
+ color: var(--bui-fg-primary);
+ }
+
+ &[data-focus-visible] {
+ box-shadow: inset 0 0 0 1px var(--bui-ring);
+ }
+
+ &[data-pressed] {
+ color: var(--bui-fg-primary);
+ }
+ }
+
+ /* ============================================================
+ Calendar (inside Popover)
+ ============================================================ */
+
+ .bui-DatePickerCalendar {
+ width: fit-content;
+ outline: none;
+ }
+
+ .bui-DatePickerCalendarHeader {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: var(--bui-space-3);
+ }
+
+ .bui-DatePickerCalendarHeading {
+ font-size: var(--bui-font-size-3);
+ font-weight: var(--bui-font-weight-bold);
+ font-family: var(--bui-font-regular);
+ color: var(--bui-fg-primary);
+ flex: 1;
+ text-align: center;
+ }
+
+ .bui-DatePickerCalendarNavButton {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ width: var(--bui-space-8);
+ height: var(--bui-space-8);
+ background: none;
+ border: none;
+ border-radius: var(--bui-radius-2);
+ color: var(--bui-fg-secondary);
+ cursor: pointer;
+ transition: background-color 0.15s ease-in-out, color 0.15s ease-in-out;
+
+ &[data-hovered] {
+ background-color: var(--bui-bg-neutral-2);
+ color: var(--bui-fg-primary);
+ }
+
+ &[data-focus-visible] {
+ outline: 2px solid var(--bui-ring);
+ outline-offset: 1px;
+ }
+
+ &[data-pressed] {
+ background-color: var(--bui-bg-neutral-2);
+ }
+ }
+
+ /* ============================================================
+ Calendar grid
+ ============================================================ */
+
+ .bui-DatePickerCalendarGrid {
+ width: 100%;
+ border-collapse: separate;
+ border-spacing: 0 2px;
+ }
+
+ .bui-DatePickerCalendarHeaderCell {
+ font-size: var(--bui-font-size-2);
+ font-weight: var(--bui-font-weight-regular);
+ color: var(--bui-fg-secondary);
+ text-align: center;
+ padding-bottom: var(--bui-space-2);
+ width: 40px;
+ }
+
+ /* ============================================================
+ Calendar cells
+ ============================================================ */
+
+ .bui-DatePickerCalendarCell {
+ position: relative;
+ width: 40px;
+ height: 40px;
+ padding: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ outline: none;
+ cursor: default;
+ font-size: var(--bui-font-size-3);
+ font-family: var(--bui-font-regular);
+ color: var(--bui-fg-primary);
+ border-radius: var(--bui-radius-full);
+
+ &[data-outside-month] {
+ color: var(--bui-fg-disabled);
+ }
+
+ &[data-disabled] {
+ color: var(--bui-fg-disabled);
+ cursor: not-allowed;
+ }
+
+ &[data-unavailable] {
+ color: var(--bui-fg-disabled);
+ cursor: not-allowed;
+ text-decoration: line-through;
+ }
+
+ &[data-hovered]:not([data-disabled]):not([data-unavailable]):not(
+ [data-selected]
+ ) {
+ background-color: var(--bui-bg-neutral-2);
+ }
+
+ &[data-focus-visible] {
+ outline: 2px solid var(--bui-ring);
+ outline-offset: -2px;
+ }
+
+ /* Today marker */
+ &[data-today]:not([data-selected]) {
+ font-weight: var(--bui-font-weight-bold);
+ }
+
+ /* Selected day */
+ &[data-selected] {
+ background-color: var(--bui-bg-solid);
+ color: var(--bui-fg-solid);
+ }
+ }
+}
diff --git a/packages/ui/src/components/DatePicker/DatePicker.stories.tsx b/packages/ui/src/components/DatePicker/DatePicker.stories.tsx
new file mode 100644
index 0000000000..5553c094f6
--- /dev/null
+++ b/packages/ui/src/components/DatePicker/DatePicker.stories.tsx
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2025 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 preview from '../../../../../.storybook/preview';
+import { DatePicker } from './DatePicker';
+import {
+ parseDate,
+ today,
+ getLocalTimeZone,
+ isWeekend,
+} from '@internationalized/date';
+import { useLocale, Form } from 'react-aria-components';
+import { Button } from '../Button';
+
+const meta = preview.meta({
+ title: 'Backstage UI/DatePicker',
+ component: DatePicker,
+ args: {
+ style: { width: 280 },
+ },
+});
+
+export const Default = meta.story({
+ args: {},
+});
+
+export const WithLabel = meta.story({
+ args: {
+ label: 'Date',
+ },
+});
+
+export const WithDescription = meta.story({
+ args: {
+ label: 'Date',
+ description: 'Select the date of your event.',
+ },
+});
+
+export const WithDefaultValue = meta.story({
+ args: {
+ label: 'Booking date',
+ defaultValue: parseDate('2025-02-03'),
+ },
+});
+
+export const Sizes = meta.story({
+ args: {
+ label: 'Date',
+ },
+ render: args => (
+
+
+
+
+ ),
+});
+
+export const Required = meta.story({
+ args: {
+ label: 'Trip date',
+ isRequired: true,
+ },
+ render: args => (
+
+ ),
+});
+
+export const Disabled = meta.story({
+ args: {
+ label: 'Date',
+ isDisabled: true,
+ defaultValue: parseDate('2025-03-01'),
+ },
+});
+
+export const Invalid = meta.story({
+ args: {
+ label: 'Date',
+ isInvalid: true,
+ errorMessage: 'The selected date is not available.',
+ defaultValue: parseDate('2025-04-01'),
+ },
+});
+
+export const WithMinMaxValue = meta.story({
+ args: {
+ label: 'Date',
+ description: 'You can only select dates within the next 30 days.',
+ minValue: today(getLocalTimeZone()),
+ maxValue: today(getLocalTimeZone()).add({ days: 30 }),
+ },
+});
+
+/**
+ * Weekends are marked unavailable and cannot be selected.
+ */
+export const WithUnavailableDates = meta.story({
+ render: args => {
+ const { locale } = useLocale();
+ return (
+ isWeekend(date, locale)}
+ />
+ );
+ },
+});
diff --git a/packages/ui/src/components/DatePicker/DatePicker.tsx b/packages/ui/src/components/DatePicker/DatePicker.tsx
new file mode 100644
index 0000000000..e88c819a99
--- /dev/null
+++ b/packages/ui/src/components/DatePicker/DatePicker.tsx
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2025 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 { forwardRef, useEffect } from 'react';
+import { DatePicker as AriaDatePicker } from 'react-aria-components';
+import { FieldLabel } from '../FieldLabel';
+import { FieldError } from '../FieldError';
+import { Popover } from '../Popover';
+import { DatePickerGroup } from './DatePickerGroup';
+import { DatePickerCalendar } from './DatePickerCalendar';
+import { useDefinition } from '../../hooks/useDefinition';
+import { DatePickerDefinition } from './definition';
+import type { DatePickerProps } from './types';
+
+/**
+ * A date picker that combines a date field and a calendar popover, allowing
+ * users to enter or select a date with full keyboard and screen reader
+ * accessibility.
+ *
+ * @public
+ */
+export const DatePicker = forwardRef(
+ (props, ref) => {
+ const { ownProps, restProps, dataAttributes } = useDefinition(
+ DatePickerDefinition,
+ props,
+ );
+
+ const { classes, label, description, secondaryLabel } = ownProps;
+
+ const ariaLabel = restProps['aria-label'];
+ const ariaLabelledBy = restProps['aria-labelledby'];
+
+ useEffect(() => {
+ if (!label && !ariaLabel && !ariaLabelledBy) {
+ console.warn(
+ 'DatePicker requires either a visible label, aria-label, or aria-labelledby for accessibility',
+ );
+ }
+ }, [label, ariaLabel, ariaLabelledBy]);
+
+ const secondaryLabelText =
+ secondaryLabel || (restProps.isRequired ? 'Required' : null);
+
+ return (
+
+
+
+
+
+
+
+
+ );
+ },
+);
+
+DatePicker.displayName = 'DatePicker';
diff --git a/packages/ui/src/components/DatePicker/DatePickerCalendar.tsx b/packages/ui/src/components/DatePicker/DatePickerCalendar.tsx
new file mode 100644
index 0000000000..4c03bf197b
--- /dev/null
+++ b/packages/ui/src/components/DatePicker/DatePickerCalendar.tsx
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2025 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 {
+ Calendar,
+ CalendarGrid,
+ CalendarGridHeader,
+ CalendarHeaderCell,
+ CalendarGridBody,
+ CalendarCell,
+ Heading,
+ Button,
+} from 'react-aria-components';
+import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react';
+import { useDefinition } from '../../hooks/useDefinition';
+import { DatePickerCalendarDefinition } from './definition';
+
+/**
+ * Calendar popover content for DatePicker — renders the Calendar with
+ * navigation and a full calendar grid.
+ *
+ * @internal
+ */
+export const DatePickerCalendar = () => {
+ const { ownProps } = useDefinition(DatePickerCalendarDefinition, {});
+ const { classes } = ownProps;
+
+ return (
+
+
+
+
+
+
+
+
+ {day => (
+
+ {day}
+
+ )}
+
+
+ {date => }
+
+
+
+ );
+};
diff --git a/packages/ui/src/components/DatePicker/DatePickerGroup.tsx b/packages/ui/src/components/DatePicker/DatePickerGroup.tsx
new file mode 100644
index 0000000000..5e9d4dc76e
--- /dev/null
+++ b/packages/ui/src/components/DatePicker/DatePickerGroup.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2025 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 { Group, DateInput, DateSegment, Button } from 'react-aria-components';
+import { RiCalendarLine } from '@remixicon/react';
+import { useDefinition } from '../../hooks/useDefinition';
+import { DatePickerGroupDefinition } from './definition';
+
+/**
+ * Custom field group for DatePicker — renders a single DateInput field
+ * and a calendar trigger button.
+ *
+ * @internal
+ */
+export const DatePickerGroup = ({ dataSize }: { dataSize?: string }) => {
+ const { ownProps, dataAttributes } = useDefinition(
+ DatePickerGroupDefinition,
+ {},
+ );
+ const { classes } = ownProps;
+
+ return (
+
+
+ {segment => (
+
+ )}
+
+
+
+ );
+};
diff --git a/packages/ui/src/components/DatePicker/definition.ts b/packages/ui/src/components/DatePicker/definition.ts
new file mode 100644
index 0000000000..f082355ccf
--- /dev/null
+++ b/packages/ui/src/components/DatePicker/definition.ts
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2025 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 { defineComponent } from '../../hooks/useDefinition';
+import type { DatePickerOwnProps } from './types';
+import styles from './DatePicker.module.css';
+
+/**
+ * Component definition for DatePicker
+ * @public
+ */
+export const DatePickerDefinition = defineComponent()({
+ styles,
+ classNames: {
+ root: 'bui-DatePicker',
+ },
+ propDefs: {
+ size: { dataAttribute: true, default: 'small' },
+ className: {},
+ label: {},
+ description: {},
+ secondaryLabel: {},
+ },
+});
+
+/**
+ * Component definition for DatePickerGroup
+ * @public
+ */
+export const DatePickerGroupDefinition = defineComponent<
+ Record
+>()({
+ styles,
+ classNames: {
+ root: 'bui-DatePickerGroup',
+ dateInput: 'bui-DatePickerDateInput',
+ segment: 'bui-DatePickerSegment',
+ button: 'bui-DatePickerButton',
+ },
+ bg: 'consumer',
+ propDefs: {},
+});
+
+/**
+ * Component definition for DatePickerCalendar
+ * @public
+ */
+export const DatePickerCalendarDefinition = defineComponent<
+ Record
+>()({
+ styles,
+ classNames: {
+ root: 'bui-DatePickerCalendar',
+ header: 'bui-DatePickerCalendarHeader',
+ heading: 'bui-DatePickerCalendarHeading',
+ navButton: 'bui-DatePickerCalendarNavButton',
+ grid: 'bui-DatePickerCalendarGrid',
+ gridHeader: 'bui-DatePickerCalendarGridHeader',
+ headerCell: 'bui-DatePickerCalendarHeaderCell',
+ gridBody: 'bui-DatePickerCalendarGridBody',
+ cell: 'bui-DatePickerCalendarCell',
+ },
+ propDefs: {},
+});
diff --git a/packages/ui/src/components/DatePicker/index.ts b/packages/ui/src/components/DatePicker/index.ts
new file mode 100644
index 0000000000..60d7a8d78a
--- /dev/null
+++ b/packages/ui/src/components/DatePicker/index.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2025 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 './DatePicker';
+export * from './types';
+export {
+ DatePickerDefinition,
+ DatePickerGroupDefinition,
+ DatePickerCalendarDefinition,
+} from './definition';
diff --git a/packages/ui/src/components/DatePicker/types.ts b/packages/ui/src/components/DatePicker/types.ts
new file mode 100644
index 0000000000..fb98ce407b
--- /dev/null
+++ b/packages/ui/src/components/DatePicker/types.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2025 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 type { DatePickerProps as AriaDatePickerProps } from 'react-aria-components';
+import type { DateValue } from '@internationalized/date';
+import type { Breakpoint } from '../../types';
+import type { FieldLabelProps } from '../FieldLabel/types';
+
+/** @public */
+export type DatePickerOwnProps = {
+ /**
+ * The size of the date picker
+ * @defaultValue 'small'
+ */
+ size?: 'small' | 'medium' | Partial>;
+
+ className?: string;
+
+ label?: FieldLabelProps['label'];
+ description?: FieldLabelProps['description'];
+ secondaryLabel?: FieldLabelProps['secondaryLabel'];
+};
+
+/** @public */
+export interface DatePickerProps
+ extends Omit, 'className' | 'children'>,
+ DatePickerOwnProps {}
diff --git a/packages/ui/src/definitions.ts b/packages/ui/src/definitions.ts
index ed7628f293..cb71f5062e 100644
--- a/packages/ui/src/definitions.ts
+++ b/packages/ui/src/definitions.ts
@@ -37,6 +37,11 @@ export { CheckboxDefinition } from './components/Checkbox/definition';
export { CheckboxGroupDefinition } from './components/CheckboxGroup/definition';
export { ComboboxDefinition } from './components/Combobox/definition';
export { ContainerDefinition } from './components/Container/definition';
+export {
+ DatePickerDefinition,
+ DatePickerGroupDefinition,
+ DatePickerCalendarDefinition,
+} from './components/DatePicker/definition';
export { DateRangePickerDefinition } from './components/DateRangePicker/definition';
export { DialogDefinition } from './components/Dialog/definition';
export { FieldErrorDefinition } from './components/FieldError/definition';
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 106adb4cd0..bc29073767 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -34,6 +34,7 @@ export * from './components/Avatar';
export * from './components/Badge';
export * from './components/Button';
export * from './components/Card';
+export * from './components/DatePicker';
export * from './components/DateRangePicker';
export * from './components/Dialog';
export * from './components/FieldLabel';