Skip to content

Enhance classic editor timestamp fields with native controls - #12275

Open
poligilad-auto wants to merge 1 commit into
WordPress:trunkfrom
poligilad-auto:poligilad-auto/core-editor-trac-pr
Open

Enhance classic editor timestamp fields with native controls#12275
poligilad-auto wants to merge 1 commit into
WordPress:trunkfrom
poligilad-auto:poligilad-auto/core-editor-trac-pr

Conversation

@poligilad-auto

@poligilad-auto poligilad-auto commented Jun 23, 2026

Copy link
Copy Markdown

Summary

  • Adds server-rendered native date and time controls as a progressive enhancement for the classic editor publish timestamp UI.
  • Keeps the existing touch_time() granular fields as the source of truth and fallback, syncing the native values back to aa, mm, jj, hh, and mn.
  • Shows the site's timezone next to the enhanced controls, matching the scheduling model more closely and making the site-time context explicit.

Trac ticket: https://core.trac.wordpress.org/ticket/61652

Screenshots

Publish box - date   time - core - before 2 Publish box - date   time - core - after 2

Testing

  • Opened the classic post editor locally and verified the Publish timestamp field renders as separate native date and time inputs with a visible site-time note.
  • Verified changing the native inputs updates the existing legacy timestamp fields.
  • Verified an invalid native value keeps the timestamp editor open and marks the field invalid.
  • Verified the existing granular timestamp fields remain in the DOM as the fallback/source of truth.
  • Ran node .context/classic-datetime-smoke.mjs.
  • Ran npx grunt jshint:core.
  • Ran php -l src/wp-admin/includes/template.php.
  • Ran vendor/bin/phpcs --standard=phpcs.xml.dist src/wp-admin/includes/template.php.

@github-actions

Copy link
Copy Markdown

Hi there! 👋

Thank you for your contribution to WordPress! 💖

It looks like this is your first pull request to wordpress-develop. Here are a few things to be aware of that may help you out!

No one monitors this repository for new pull requests. Pull requests must be attached to a Trac ticket to be considered for inclusion in WordPress Core. To attach a pull request to a Trac ticket, please include the ticket's full URL in your pull request description.

Pull requests are never merged on GitHub. The WordPress codebase continues to be managed through the SVN repository that this GitHub repository mirrors. Please feel free to open pull requests to work on any contribution you are making.

More information about how GitHub pull requests can be used to contribute to WordPress can be found in the Core Handbook.

Please include automated tests. Including tests in your pull request is one way to help your patch be considered faster. To learn about WordPress' test suites, visit the Automated Testing page in the handbook.

If you have not had a chance, please review the Contribute with Code page in the WordPress Core Handbook.

The Developer Hub also documents the various coding standards that are followed:

Thank you,
The WordPress Project

@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props poligilad, tyxla, jillq.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@tyxla tyxla left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for this work @poligilad-auto.

I've got some higher-level design and approach questions, but I'll post them on the ticket.

Left a few questions on the code in the meantime. The biggest one is that handling the field rendering with JS is subpar and should ideally be done in the PHP template.

node .context/classic-datetime-smoke.mjs

I wasn't able to where this is coming from

Comment thread src/js/_enqueues/admin/post.js Outdated
updateNativeTimestampFields();
$timestampdiv.addClass( 'has-native-timestamp-fields' );
$timestampwrap.before( $nativeTimestampWrap ).hide();
$nativeInput.on( 'change input', updateFieldsFromNativeTimestamp );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Binding to input runs the full-format regex on every keystroke, so while someone is mid-typing a partial value (2026-06-2) it fails the match and adds form-invalid, and the field flashes red as they type. Could we validate on change only (and update the legacy fields there), and avoid flagging invalid on transient input events? If we want live sync, we could debounce, but the invalid styling shouldn't fire until the user commits the value.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated. The native controls now listen on change only, so the full-format validation does not run while a user is still mid-typing. Invalid styling is only applied after the value is committed/saved.

Comment thread src/js/_enqueues/admin/post.js Outdated
Comment on lines 1015 to 1020
if ( updateFieldsFromNativeTimestamp && ! updateFieldsFromNativeTimestamp() ) {
event.preventDefault();
return;
}

if ( updateText() ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

updateFieldsFromNativeTimestamp() already returns updateText(), and then the existing if ( updateText() ) below calls it again, making it run twice per save. Likely we can lean on the first call's return value instead of re-invoking.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated. updateFieldsFromNativeTimestamp() is now the single save path and the save handler uses its return value instead of calling updateText() again.

Comment thread src/js/_enqueues/admin/post.js Outdated
$( '#mm' ).val( matches[2] );
$( '#jj' ).val( matches[3] );
$( '#hh' ).val( matches[4] );
$( '#mn' ).val( matches[5] );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The native control has no seconds and we don't touch #ss here, so the original seconds are carried through to submit. That actually matches the legacy behavior (seconds live in a hidden field there too), so it's not a data bug, but it's worth a one-line comment noting it's intentional, since the visible time no longer reflects the full stored value.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated. Added a comment noting that seconds intentionally remain in the existing hidden #ss field, matching the legacy timestamp UI.

$timestampdiv.addClass( 'has-native-timestamp-fields' );
$timestampwrap.before( $nativeTimestampWrap ).hide();
$nativeInput.on( 'change input', updateFieldsFromNativeTimestamp );
$timestampwrap.find( 'input, select' ).on( 'change', updateNativeTimestampFields );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These are now hidden, no?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes. In the enhanced UI those legacy controls are hidden, but they remain the source of truth/fallback and are still synced from the native controls before save.

Comment thread src/js/_enqueues/admin/post.js Outdated
$('#aa').val($('#hidden_aa').val());
$('#hh').val($('#hidden_hh').val());
$('#mn').val($('#hidden_mn').val());
if ( updateNativeTimestampFields ) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Those checks may be easy to forget and clutter the code, would be nice to figure something else out, like defaulting them to no-ops.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated. The native timestamp helpers now default to no-op behavior, so the save/cancel handlers do not need repeated existence checks.

}

toNativeValue = function() {
return $( '#aa' ).val() + '-' +

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we pad years to 4 digits too?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point. The native date value is now rendered and synced in the expected YYYY-MM-DD shape.

Comment thread src/wp-admin/css/edit.css Outdated
margin: 3px 0 0;
}

#timestampdiv .timestamp-native-wrap + p {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This couples spacing to the sibling DOM order; if the markup around the wrap ever shifts, the spacing silently breaks. Could we target a class on the element we actually want to space instead of + p?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated. The spacing now targets explicit timestamp classes instead of relying on sibling DOM order.

Comment thread src/js/_enqueues/admin/post.js Outdated
Comment on lines +890 to +893
'<p class="timestamp-native-wrap hide-if-no-js">' +
'<label for="publish-datetime-native" class="screen-reader-text">' + __( 'Date and time' ) + '</label>' +
'<input type="datetime-local" id="publish-datetime-native" class="form-required" />' +
'</p>'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The rest of this UI is server-rendered in touch_time(). Building a new labeled field as a concatenated HTML string in JS diverges from that pattern, makes the field invisible to anything inspecting server output, and splits the timestamp UI across two rendering models. I think we should render that on the PHP side instead.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated. The native date/time fields are now rendered server-side in touch_time(), and the JS only activates/syncs them when native date and time inputs are supported.

@poligilad-auto
poligilad-auto force-pushed the poligilad-auto/core-editor-trac-pr branch from e7df860 to 64c413f Compare June 29, 2026 08:34
@poligilad-auto poligilad-auto changed the title Enhance classic editor timestamp field with datetime-local Enhance classic editor timestamp fields with native controls Jun 29, 2026
@poligilad-auto
poligilad-auto force-pushed the poligilad-auto/core-editor-trac-pr branch 6 times, most recently from 31e039f to 76129d2 Compare June 29, 2026 09:13
@poligilad-auto
poligilad-auto force-pushed the poligilad-auto/core-editor-trac-pr branch from 76129d2 to 965b41d Compare June 29, 2026 09:32
@poligilad-auto

Copy link
Copy Markdown
Author

Thanks for the review, @tyxla.

I updated the PR based on the ticket discussion and this feedback.

The approach no longer uses a single datetime-local input. It now uses separate native date and time inputs, stacked vertically in the Publish metabox.

I think this addresses the main concerns more directly:

  • It avoids relying on a combined browser date/time picker.
  • It keeps the date and time easier to understand as separate values.
  • It fits the narrow Publish metabox better now that the fields are stacked.
  • It adds an explicit “Site time” note below the fields, which is closer to the newer editor/product editor pattern and clarifies which timezone the value represents.
  • The existing aa, mm, jj, hh, and mn fields remain the source of truth/fallback, so the submitted data shape is unchanged.
  • The native fields are now rendered server-side in touch_time(), with JS only activating and syncing them when supported.

I’ll reply to the code-specific comments inline as well.

@tyxla tyxla left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Left a review, and from the code perspective, didn't see anything major. Left a few suggestions.

The biggest concerns here remain:

  • Ensuring / gathering feedback and confirming this is the design we want to go with
  • Ensuring this doesn't break any existing contracts that would cause breakage of existing plugins integrating with the pre-existing controls

/* translators: Hidden accessibility text. */
_e( 'Edit date and time' );
?>
<span class="timestamp-display">

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Noting that some of these markup changes might be necessary below in some of the other meta boxes. Or we need to ensure that our CSS changes affect only this one. But right now the changes may affect the other meta boxes in weird ways because some of those markup changes are missing

Comment thread src/wp-admin/css/edit.css
Comment on lines +568 to +570
.misc-pub-curtime #timestamp:before {
content: none;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is duplicated

Comment on lines +789 to +792
$timestampdiv.find( '.timestamp-wrap, #publish-date-native' ).addClass( 'form-invalid' );
return false;
} else {
$timestampdiv.find('.timestamp-wrap').removeClass('form-invalid');
$timestampdiv.find( '.timestamp-wrap, .timestamp-native-wrap input' ).removeClass( 'form-invalid' );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we make these selectors consistent?

Comment on lines +834 to +838
$timezone = wp_timezone_string();
if ( preg_match( '/^([+-])(\d{2}):(\d{2})$/', $timezone, $timezone_matches ) ) {
$timezone = 'UTC' . $timezone_matches[1] . (int) $timezone_matches[2];
$timezone .= ( '00' === $timezone_matches[3] ) ? '' : ':' . $timezone_matches[3];
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

UTC+0 might be weird - should it just be UTC?

j111q added a commit to j111q/wordpress-develop that referenced this pull request Aug 14, 2026
Follow-up to the review on PR WordPress#12275 by @tyxla:

- Remove a duplicated `.misc-pub-curtime #timestamp:before` rule, keeping the
  later declaration so it still overrides `.curtime #timestamp:before` (equal
  specificity, so source order decides).
- Align the native date/time invalid-state selectors: mark and clear
  `form-invalid` on both native inputs via `.timestamp-native-wrap input`,
  instead of flagging only `#publish-date-native` by ID.
- Render a zero UTC offset as `UTC` rather than `UTC+0` in the site-time note.
- Size the Publish-box timestamp icon column with `auto` instead of a fixed
  `27px`, so the row's label lines up with the sibling meta rows.

Props poligilad, tyxla.
See WordPress#12275.
@j111q

j111q commented Aug 14, 2026

Copy link
Copy Markdown

I've opened #13050 as a draft, which carries Poli's commit unchanged and adds a follow-up commit addressing the code comments from @tyxla's last review.

  • Removed the duplicated .misc-pub-curtime #timestamp:before rule
  • Made the native inputs' invalid-state selectors symmetric
  • Rendered a zero timezone offset as UTC rather than UTC+0.
  • Fixed the Publish-box alignment: the timestamp row's icon column was a fixed 27px, which pushed its label ~10px right of the sibling rows — switching it to auto lines them all up, and the new CSS stays scoped to .misc-pub-curtime so the other meta boxes are untouched.

(Thanks Marin!)

j111q added a commit to j111q/wordpress-develop that referenced this pull request Aug 14, 2026
…pat.

Wrapping the timestamp display and the Edit link in a `.timestamp-display`
span moved `a.edit-timestamp` out from being a sibling of `#timestampdiv`. A
scan of the plugin directory found several plugins (e.g. PublishPress
Statuses, Media Library Assistant, LH Archived Post Status) that rely on
`$( '#timestampdiv' ).siblings( 'a.edit-timestamp' )`, which the wrapper would
break.

Keep `#timestamp` and `a.edit-timestamp` as direct children of
`.misc-pub-curtime` and lay the row out with a three-column grid instead. The
rendered result and the row alignment are unchanged.

See WordPress#12275.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants