Skip to content

[temporal] New DateField, TimeField and DateTimeField components#3666

Open
flaviendelangle wants to merge 57 commits intomui:masterfrom
flaviendelangle:date-field-component
Open

[temporal] New DateField, TimeField and DateTimeField components#3666
flaviendelangle wants to merge 57 commits intomui:masterfrom
flaviendelangle:date-field-component

Conversation

@flaviendelangle
Copy link
Copy Markdown
Member

@flaviendelangle flaviendelangle commented Jan 2, 2026

Follow up on #1973
Part of #1709

I'm doing to tests to see if the Calendar internals as suitable for the next steps.

To read before reviewing

  • The TValue generic is always equal to TemporalFieldSupportedObject. I kept it because it will be needed for the range fields if we keep the MUI X approach.

@flaviendelangle flaviendelangle self-assigned this Jan 2, 2026
@flaviendelangle flaviendelangle added the type: new feature Expand the scope of the product to solve a new problem. label Jan 2, 2026
@flaviendelangle flaviendelangle changed the title Date field component [date field] New component Jan 2, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Jan 2, 2026

commit: a9692df

@mui-bot
Copy link
Copy Markdown

mui-bot commented Jan 2, 2026

Bundle size report

Bundle Parsed size Gzip size
@base-ui/react 0B(0.00%) 0B(0.00%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

@netlify
Copy link
Copy Markdown

netlify bot commented Jan 2, 2026

Deploy Preview for base-ui ready!

Name Link
🔨 Latest commit a9692df
🔍 Latest deploy log https://app.netlify.com/projects/base-ui/deploys/69c6b82ef47ba0000800675b
😎 Deploy Preview https://deploy-preview-3666--base-ui.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions bot added PR: out-of-date The pull request has merge conflicts and can't be merged. and removed PR: out-of-date The pull request has merge conflicts and can't be merged. labels Jan 5, 2026
@flaviendelangle flaviendelangle changed the title [date field] New component [temporal] New DateField component Jan 8, 2026
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jan 14, 2026
@flaviendelangle flaviendelangle changed the title [temporal] New DateField component [temporal] New DateField and TimeField component Jan 15, 2026
@flaviendelangle flaviendelangle changed the title [temporal] New DateField and TimeField component [temporal] New DateField and TimeField components Jan 15, 2026
@flaviendelangle flaviendelangle changed the title [temporal] New DateField and TimeField components [temporal] New DateField, TimeField and DateTimeField components Feb 13, 2026
@github-actions github-actions bot added PR: out-of-date The pull request has merge conflicts and can't be merged. and removed PR: out-of-date The pull request has merge conflicts and can't be merged. labels Feb 16, 2026
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Feb 24, 2026
@flaviendelangle flaviendelangle added the scope: temporal Changes related to the Temporal components. label Feb 24, 2026
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Feb 24, 2026
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Mar 13, 2026
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@flaviendelangle
Copy link
Copy Markdown
Member Author

I think the JSDoc could be edited: the callback reads like a fully custom renderer, but the supported customization path is "render the locale-ordered sections, optionally wrapped" with <DateField.Section render={...}> as the escape hatch for customizing section/separator elements. state.separator already gives consumers a good way to customize separators, so maybe a demo about this would help? (IMO it's just quite different from all the other components in Base UI this way, so it's confusing at first encounter)

I improved the JSDoc 👍

@atomiks
Copy link
Copy Markdown
Contributor

atomiks commented Mar 24, 2026

  1. Most demos still show invalid <label> usage. Wrapping the entire component in <label> is invalid unless the sections are <span>s to avoid invalid HTML if we want to go that route over htmlFor/id. The label font weight should also be bold.
  2. The "Submit" demos are still outdated and should match the button styles of other component demos
  3. Given each individual part is tabbable anyway, I'm not convinced we should remove the clear button from the tab order as there isn't an Esc equivalent to activate with keyboard only?
  4. The demos should show HH:MM:AP placeholders

@flaviendelangle
Copy link
Copy Markdown
Member Author

The demos should show HH:MM:AP placeholders

What makes you think that?
Both React Aria and the native browser input (on Chrome Linux at least) uses dashes

@flaviendelangle
Copy link
Copy Markdown
Member Author

Points 1, 2 and 3 applied

@atomiks
Copy link
Copy Markdown
Contributor

atomiks commented Mar 25, 2026

GPT-5.4 Pro Review

I like the direction overall. The shared temporal core looks strong, and the accessibility work is thoughtful. But I would still treat this as not merge-ready yet because the PR is very large — about +20,597 / -274 across 186 files — so any public-API ambiguity here will be expensive to unwind later. ([GitHub]1)

What I would change before merge:

  1. Tighten the public types first.
    The author explicitly notes that the TValue generic is still effectively a broad temporal-field object type for now, and the generated API tables reflect that: DateField/TimeField expose value and defaultValue as TemporalValue, while referenceDate, min, and max show up as Date. For three distinct public components, that feels too loose and internally inconsistent. I would not love merging a public API that the author already describes as something to rethink later. ([GitHub]2)

  2. Narrow the format contract in docs, or strengthen runtime validation.
    The docs currently say the format string follows date-fns format patterns, but the parser is adapter-driven, and the runtime only supports a subset of parts: weekDay, day, month, year, hours, minutes, seconds, and meridiem. Unsupported parts only trigger a development warning. That means the docs currently promise more than the implementation guarantees, and “date-fns patterns” is especially misleading if another adapter is active. ([GitHub]3)

  3. Pick one primary composition model, or document the split much more clearly.
    Root accepts a render function for sections, but useTemporalFieldRoot silently wraps that in SectionList; SectionList itself just maps sections and does not render a wrapper element. At the same time, the hand-written docs say SectionList “groups all sections into a single element,” which is simply not true. The result is a public API with two ways to render sections, plus JSDoc that suggests wrapper flexibility the implementation does not really provide in the “children as function” path. I would either make SectionList the clearly canonical layout primitive, or keep the Root callback as undocumented sugar. ([GitHub]4)

  4. Fix the repeated-part accessibility bug.
    The hidden per-part labels are generated by mapping all date parts and giving each span an id based on section.token.config.part, and each segment’s aria-labelledby points to ${id}-${part}-label. I do not see duplicate-part rejection in format validation. So if repeated-part formats are allowed — for example a text month plus a numeric month — this will create duplicate React keys and duplicate DOM ids. That is a real bug, not just a docs concern. I’d dedupe hidden labels by part, or key/id them by section index. ([GitHub]4)

  5. The separator story is workable, but not first-class enough yet.
    The good news is that DateField.Section does expose separator state and a render prop, so custom separator markup is possible while keeping the built-in wiring. The downside is that if a consumer replaces separators with their own nodes outside DateField.Section, they lose the built-in click-to-select behavior because that wiring lives in the section/separator handlers. React Aria’s DateInputDateSegment model, with segment type="literal", is much more obvious. I would at least tighten the JSDoc and add a demo showing the render={(props, state) => ...} path for separators. Longer-term, I’d seriously consider whether separator should actually be named literal. ([GitHub]5)

  6. The imperative focus story feels underpowered.
    Root renders a div plus a hidden input, inputRef points at the hidden input, and actionsRef only exposes clear. That means programmatic focus is available only through an implementation-detail ref rather than a field-shaped public action. A focus() action would make this API feel more complete and easier to use from forms and validation flows. ([GitHub]5)

  7. There is a meaningful React Aria parity gap in the semantic API.
    Base UI currently offers a lower-level surface: format, timezone, referenceDate, and for time/date-time, ampm and step. React Aria pairs its segmented model with semantic props like granularity, hourCycle, hideTimeZone, placeholderValue, and isDateUnavailable, plus an explicit DateInput container with its own state surface. Base UI’s token-level format is actually more powerful for exact layouts like EEEE, MMMM dd, yyyy or yyyy-MM-dd, but it also makes the component easier to misuse and much harder to document precisely. ([GitHub]5)

  8. I’m uneasy about zoned form submission semantics.
    The field logic preserves the input timezone when publishing onValueChange, but the form-facing hidden inputs for TimeField and DateTimeField serialize to native time and datetime-local strings. My inference is that this drops timezone information on submit, whereas React Aria documents form submission as ISO 8601 strings. If zoned values are a real target use case, I’d either document this limitation very plainly or add an alternate hidden ISO value path. ([GitHub]6)

What looks especially good:

  • The shared architecture is the right call. TimeField and DateTimeField reuse the same Section, SectionList, and Clear parts, which is exactly in the spirit of AGENTS.md’s guidance to avoid duplicating logic and to centralize shared behavior. ([GitHub]7)

  • The accessibility model is thoughtful. Individual sections are exposed as spinbuttons, they get field-level and part-level labeling, and the code clearly propagates error/description context down to each editable segment. That’s a solid baseline. ([GitHub]8)

  • The PR does put serious effort into docs and tests. It adds dedicated docs pages/demos and a sizable set of component and shared tests for the new field family. So this is not a “half-documented” feature; the remaining issues are mostly about accuracy and clarity of the public contract, not lack of effort. ([GitHub]1)

On AGENTS.md specifically: I’d say this is mostly conformant on the code side and not fully there on the docs side. The implementation shares logic well, uses the DOM-safe owner utilities AGENTS asks for, and clearly invested in tests/docs. The main miss is that some hand-written docs still overstate or contradict the actual API behavior, and AGENTS explicitly says public docs should be updated accurately when the API changes. ([GitHub]7)

My net: promising implementation, but I would block on 1–4. The React Aria gap is not that Base UI lacks power; it’s that React Aria’s model is currently easier to understand and harder to misuse.

@flaviendelangle
Copy link
Copy Markdown
Member Author

I like the direction overall. The shared temporal core looks strong, and the accessibility work is thoughtful.

Thank your GPT 🙏 😆
I'll have a deeper look at all the points later 👍

@flaviendelangle
Copy link
Copy Markdown
Member Author

Tighten the public types first.
The author explicitly notes that the TValue generic is still effectively a broad temporal-field object type for now, and the generated API tables reflect that: DateField/TimeField expose value and defaultValue as TemporalValue, while referenceDate, min, and max show up as Date. For three distinct public components, that feels too loose and internally inconsistent. I would not love merging a public API that the author already describes as something to rethink later. ([https://github.com//pull/3666]#3666)

For that one, I really think it's a bad idea to remove the TValue abstraction since we know we want to explore range field and range calendar before any public release.

@flaviendelangle
Copy link
Copy Markdown
Member Author

  1. Fix the repeated-part accessibility bug.

Fixed

@flaviendelangle
Copy link
Copy Markdown
Member Author

There is a meaningful React Aria parity gap in the semantic API.
Base UI currently offers a lower-level surface: format, timezone, referenceDate, and for time/date-time, ampm and step. React Aria pairs its segmented model with semantic props like granularity, hourCycle, hideTimeZone, placeholderValue, and isDateUnavailable, plus an explicit DateInput container with its own state surface. Base UI’s token-level format is actually more powerful for exact layouts like EEEE, MMMM dd, yyyy or yyyy-MM-dd, but it also makes the component easier to misuse and much harder to document precisely. ([GitHub]5)

This is a real DX discussion we need to have.
Maybe we could have 2 shapes for the format prop:

format?: string | TemporalFieldFormatConfig

interface TemporalFieldFormatConfig {
  granularity: TemporalFieldGranularity;
  ampm?: boolean
  // ...
}

@flaviendelangle
Copy link
Copy Markdown
Member Author

Narrow the format contract in docs, or strengthen runtime validation.
The docs currently say the format string follows date-fns format patterns, but the parser is adapter-driven, and the runtime only supports a subset of parts: weekDay, day, month, year, hours, minutes, seconds, and meridiem. Unsupported parts only trigger a development warning. That means the docs currently promise more than the implementation guarantees, and “date-fns patterns” is especially misleading if another adapter is active. ([GitHub]3)

I added a more detailed doc (+ strengthen the validation in the parsing) but we need to discuss where we should document that 👀

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering this is only used in packages/react/src/date-field/utils/TemporalFieldStore.ts, could we keep this file in that local utils folder? It would avoid making it part of the public API.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using it in the Scheduler + Tree View (on MUI X) and on the Tree PR on Base UI.
I neeed an equivalent of useTimeout for components with large stores out of React.
What problem do you see putting it in @base-ui/utils?

Copy link
Copy Markdown
Contributor

@romgrk romgrk Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine if it's used in many places, just want to make sure we don't increase the API size for no reason.

Btw, why use a manager class rather than individual timeouts? The way I do it when I need multiple timeouts somewhere is something like this:

class X {
  constructor() {
    this.mouseDownTimeout = new Timeout()
    this.mouseUpTimeout = new Timeout()
    // etc
  }
}

Admittedly, declaring multiple timeouts upfront may have a performance impact that a manager doesn't have, and the cleanup isn't as neat (currently, but we could implement the new Disposable protocol to ease it up). However, with a manager the type safety is currently not as good.

I also see that the timeout names use inconsistent casing (mix of dash-case and camelCase), and that the interval methods are never used (and also don't strictly match with the name of the class).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interval methods are only used on the Scheduler.
I can drop them in favor of an IntervalManager that I don't move to @base-ui/utils until it's used in 2-3 packages (why might never be the case).

The main downside I see with standalone timeouts is the cleanup, it's easy to miss one in the mountEffect cleanup function, especially with plugins.
But if you have a way to make them autoclean, I'm all in favor of removing the abstraction.

I'll fix the casing 👍

@LukasTy
Copy link
Copy Markdown
Member

LukasTy commented Mar 27, 2026

Follow-up observations

1. Placeholders

On X Pickers we decided to have no value when fields are unfocused to allow showing the placeholder and making all input-like fields look the same visually.
Do you think this is not relevant for Base UI Fields?

Maybe you could add a <DateField> to the forms demo to illustrate this case?

2. Forms

Enter clicks inside of a field are probably prevented, as they do not seem to submit a form.
Or in other words, we probably need to propagate it to the hidden <input>.

3. Label

<label> in demos should be unselectable with the mouse, like in Select's case. It should avoid the focus "flicker" when repeatedly clicking the label.

4. Experiments

disabled and maybe readOnly demos could use a different token label style to signal they are disabled.

P.S. Sorry I delayed this one, forgot the draft wasn't posted. 🙈

@flaviendelangle
Copy link
Copy Markdown
Member Author

Point 2, 3 and 4 solved.
For 1, I would wait for users feedback. React Aria does not support it and I can't find a way to have a good DX that is in the spirit of Base UI and SSR compatible 😢

@LukasTy
Copy link
Copy Markdown
Member

LukasTy commented Mar 27, 2026

For 1, I would wait for users feedback. React Aria does not support it and I can't find a way to have a good DX that is in the spirit of Base UI and SSR compatible 😢

Makes sense. If the team is happy with the UX of the current state, then we are GtG on this one. 👌

Point 2, 3 and 4 solved.

2. Forms

Enter clicks inside of a field are probably prevented, as they do not seem to submit a form. Or in other words, we probably need to propagate it to the hidden <input>.

The field does not seem to propagate a form submit as seen in this experiment.
If this were an <input> element, it would trigger a form submit.

Screen.Recording.2026-03-27.at.17.37.42.mov

3. Label

<label> in demos should be unselectable with the mouse, like in Select's case. It should avoid the focus "flicker" when repeatedly clicking the label.

I meant this behavior.
The <label> needs a user-select: none.

Screen.Recording.2026-03-27.at.17.32.55.mov

4. Experiments

disabled and maybe readOnly demos could use a different token label style to signal they are disabled.

Are you sure?
They all look the same to me.
In any case, this is a huge nitpick, given that these are experiments. 😆
Screenshot 2026-03-27 at 17 34 36

@flaviendelangle
Copy link
Copy Markdown
Member Author

I just didn't push the code 🤷

Comment on lines +981 to +986
const defaultButton = form.querySelector<HTMLElement>(
'input[type="submit"], button[type="submit"], button:not([type])',
);
if (defaultButton) {
defaultButton.click();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const defaultButton = form.querySelector<HTMLElement>(
'input[type="submit"], button[type="submit"], button:not([type])',
);
if (defaultButton) {
defaultButton.click();
}
form.requestSubmit();

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: out-of-date The pull request has merge conflicts and can't be merged. scope: temporal Changes related to the Temporal components. type: new feature Expand the scope of the product to solve a new problem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants