Skip to content
Back to skills

Wp Admin Form Controls

ASecurity

Use WordPress admin form-control widgets that ship in core,

  • 22 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added June 5, 2026
ai-agentsphpreactgitapidocumentation

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 21, 2026

npx -y skills add Lonsdale201/wp-agent-skills --skill wp-admin-form-controls --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Wp Admin Form Controls?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Wp Admin Form Controls
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/lonsdale201-wp-admin-form-controls/badge)](https://www.skillsdirectory.com/skills/lonsdale201-wp-admin-form-controls)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: wp-admin-form-controls
description: Use WordPress admin form-control widgets that ship in core,
  `wp-color-picker`, `jquery-ui-datepicker`, `jquery-ui-autocomplete`,
  and `wp-pointer`. Covers correct script/style enqueues, the missing
  jQuery UI datepicker CSS, `wpColorPicker` change / clear callbacks,
  datepicker `yy-mm-dd` formatting plus strict server sanitization,
  autocomplete `source` shapes with `response()`, core user/tag suggest,
  and `wp-pointer` dismissal through `dismiss-wp-pointer`. Use when adding
  color, date, typeahead, or first-run pointer controls to settings pages,
  metaboxes, or repeater rows.
metadata:
  wp-skills-author: "Soczó Kristóf"
  wp-skills-contact: "mailto:lonsdale201@hotmail.com"
  wp-skills-plugin: "wordpress"
  wp-skills-plugin-version-tested: "6.0 - 7.1"
  wp-skills-wp-version-tested: "7.1"
  wp-skills-php-min: "7.4"
  wp-skills-last-updated: "2026-08-20"
---

# WordPress Admin Form Controls

Four small widgets that ship with every WP install and that plugin developers reach for daily — but rarely enqueue correctly. This skill is the recipe sheet.

## When to use this skill

Trigger when ANY of the following is true:

- A plugin admin field needs a color picker, a date picker, a typeahead/autocomplete, or a first-run tooltip on a new feature.
- Code references `wp-color-picker`, `wpColorPicker`, `iris`, `jquery-ui-datepicker`, `jquery-ui-autocomplete`, `wp-pointer`, or `$('#x').pointer()`.
- The user is about to bundle their own color picker (Pickr, color.js, Coloris) or date picker (flatpickr, Pikaday) when core's would do.
- The user complains "the datepicker has no CSS" / "wpColorPicker is not a function".

## Color picker — `wp-color-picker`

Script handle `wp-color-picker` depends on `iris` (the underlying picker — Automattic's color library, `wp-includes/js/iris.min.js`, registered at `wp-includes/script-loader.php:1502`). The stylesheet `wp-color-picker` is also registered; you must enqueue it as well.

### Enqueue

```php
add_action( 'admin_enqueue_scripts', static function ( string $hook_suffix ): void {
    if ( 'settings_page_myplugin' !== $hook_suffix ) {
        return;
    }
    wp_enqueue_script( 'wp-color-picker' );
    wp_enqueue_style( 'wp-color-picker' );

    wp_enqueue_script(
        'myplugin-color-init',
        plugins_url( 'assets/color-init.js', MYPLUGIN_FILE ),
        array( 'wp-color-picker', 'wp-i18n' ),
        MYPLUGIN_VERSION,
        array( 'in_footer' => true )
    );
} );
```

### Markup + init

```php
<input
    type="text"
    name="myplugin_options[brand_color]"
    value="<?php echo esc_attr( $options['brand_color'] ?? '#0073aa' ); ?>"
    class="myplugin-color-field"
    data-default-color="#0073aa"
/>
```

```js
jQuery( function ( $ ) {
    $( '.myplugin-color-field' ).wpColorPicker( {
        // Optional — picked up automatically from data-default-color if set on the input.
        // defaultColor: '#0073aa',

        change: function ( event, ui ) {
            // Fires on every color tweak while the picker is open.
            // ui.color is an Iris Color object — call .toString() for the hex.
        },
        clear: function () {
            // Fires when the "Clear" button is clicked.
        },
        palettes: true,                              // false to hide the preset palette row
        // palettes: [ '#0073aa', '#23282d', '#fff' ], // OR an array of hex strings
    } );
} );
```

### Sanitizing server-side

```php
'sanitize_callback' => static function ( $value ): string {
    if ( ! is_string( $value ) ) {
        return '';
    }
    return sanitize_hex_color( $value ) ?: '';
},
```

`sanitize_hex_color()` returns `null` on invalid input — coalesce to `''` (or your default) to keep `update_option()` happy.

## Date picker — `jquery-ui-datepicker`

The bundled jQuery UI datepicker. The non-obvious bit: **core does NOT enqueue
a default stylesheet for it**. Ship a small plugin-owned stylesheet; avoid
making wp-admin depend on a third-party CDN. For a simple date-only value,
prefer native `<input type="date">` and use jQuery UI only when you need a
consistent calendar UI or constraints native controls cannot provide.

### Enqueue

```php
add_action( 'admin_enqueue_scripts', static function ( string $hook_suffix ): void {
    if ( 'settings_page_myplugin' !== $hook_suffix ) {
        return;
    }
    wp_enqueue_script( 'jquery-ui-datepicker' );

    // CRITICAL — core ships no datepicker CSS. Ship your own:
    wp_enqueue_style(
        'myplugin-datepicker',
        plugins_url( 'assets/datepicker.css', MYPLUGIN_FILE ),
        array(),
        MYPLUGIN_VERSION
    );

    wp_enqueue_script(
        'myplugin-date-init',
        plugins_url( 'assets/date-init.js', MYPLUGIN_FILE ),
        array( 'jquery-ui-datepicker', 'wp-i18n' ),
        MYPLUGIN_VERSION,
        array( 'in_footer' => true )
    );

    wp_add_inline_script(
        'myplugin-date-init',
        'window.MyPluginDates = ' . wp_json_encode( array(
            'firstDay' => (int) get_option( 'start_of_week', 0 ),
        ) ) . ';',
        'before'
    );
} );
```

If you don't want to bundle your own CSS, the jQuery UI "smoothness" theme CSS works:

```css
/* assets/datepicker.css — minimum the picker needs to be usable */
.ui-datepicker { background: #fff; border: 1px solid #c3c4c7; padding: 8px; z-index: 9999; }
.ui-datepicker-header { display: flex; justify-content: space-between; padding: 4px 0; }
.ui-datepicker-prev, .ui-datepicker-next { cursor: pointer; }
.ui-datepicker table { border-collapse: collapse; }
.ui-datepicker td a { display: block; padding: 4px 8px; text-align: center; text-decoration: none; }
.ui-datepicker td a.ui-state-active { background: #2271b1; color: #fff; }
```

### Markup + init

```php
<input
    type="text"
    name="myplugin_options[start_date]"
    value="<?php echo esc_attr( $options['start_date'] ?? '' ); ?>"
    class="myplugin-date-field"
    autocomplete="off"
/>
```

```js
jQuery( function ( $ ) {
    $( '.myplugin-date-field' ).datepicker( {
        dateFormat:      'yy-mm-dd',           // ISO format for storage. NOT PHP's date() format — jQuery UI's.
        firstDay:        MyPluginDates.firstDay,
        changeMonth:     true,
        changeYear:      true,
        yearRange:       '-5:+5',
        showButtonPanel: true,
    } );
} );
```

`autocomplete="off"` on the input prevents the browser from popping its own calendar overlay on top of the jQuery UI one.

`dateFormat` is jQuery UI's own format string (`yy-mm-dd` = 4-digit year, 2-digit month, 2-digit day) — NOT PHP's `date()` syntax. Common confusion source.

### Sanitizing server-side

```php
'sanitize_callback' => static function ( $value ): string {
    $raw  = trim( (string) $value );
    $date = DateTimeImmutable::createFromFormat( '!Y-m-d', $raw );
    $err  = DateTimeImmutable::getLastErrors();

    if ( ! $date || ( is_array( $err ) && ( $err['warning_count'] || $err['error_count'] ) ) ) {
        return '';
    }

    return $date->format( 'Y-m-d' ) === $raw ? $raw : '';
},
```

## Autocomplete — `jquery-ui-autocomplete`

Handle `jquery-ui-autocomplete` (depends on `jquery-ui-menu` and `wp-a11y` — the a11y dep means screen readers get role announcements for free).

### Enqueue

```php
wp_enqueue_script(
    'myplugin-tag-suggest',
    plugins_url( 'assets/tag-suggest.js', MYPLUGIN_FILE ),
    array( 'jquery-ui-autocomplete', 'wp-api-fetch', 'wp-i18n' ),
    MYPLUGIN_VERSION,
    array( 'in_footer' => true )
);
```

### Source shapes

`source` can be a static array, a synchronous transform, or an async function that calls `response( results )` after `wp.apiFetch()`. It cannot just return a Promise. Items can be strings or objects with at least `label` and `value`; add an `id` and read it in `select` when you need a hidden ID field. See `reference.md` for complete examples.

For user / term suggestions, core ships `user-suggest` (admin pages only) and `tags-suggest` — those are wrappers around `jquery-ui-autocomplete` that hit core admin-ajax endpoints. Worth reusing if your "User" autocomplete maps to WP users — see `wp-admin/js/user-suggest.js`.

## Admin onboarding pointer — `wp-pointer`

The blue floating tooltip core uses for "new feature" onboarding (e.g. the first-time pointer that introduced the Customizer). Useful in plugins for: announcing a new admin menu item after a version bump, drawing attention to a moved button, first-time-tour-style hints.

This is not WordPress 7.1's `wp_get_tooltip()` / `wp_get_toggletip()` API.
Pointers are dismissible onboarding UI with user-meta persistence; tooltips are
accessible control names or supporting context. Reach for that core API
when the requested UI is a tooltip/toggletip rather than a one-time tour.

Handle `wp-pointer` is registered at `wp-includes/script-loader.php:860` and depends on `jquery-ui-core`. The matching stylesheet `wp-pointer` is registered at `:1655` and depends on `dashicons` — enqueue both.

### Dismissal persistence pattern

The hard part isn't showing the pointer; it is not showing it again after dismissal. Core stores dismissed pointer slugs in `dismissed_wp_pointers` user meta. Enqueue `wp-pointer` + style only when the slug is not already dismissed, then POST `{ action: 'dismiss-wp-pointer', pointer: slug }` in the pointer `close` callback. See `reference.md` for the full safe-content example.

## Combining multiple controls on one page

The handles compose cleanly — declare them all as deps, init each in DOM-ready. WP loads each only once even if multiple scripts depend on it.

```php
wp_enqueue_script( 'wp-color-picker' );
wp_enqueue_style( 'wp-color-picker' );
wp_enqueue_script( 'jquery-ui-datepicker' );
wp_enqueue_style( 'myplugin-datepicker' );
wp_enqueue_script(
    'myplugin-fields',
    plugins_url( 'assets/fields.js', MYPLUGIN_FILE ),
    array( 'wp-color-picker', 'jquery-ui-datepicker', 'jquery-ui-autocomplete', 'wp-api-fetch', 'wp-i18n' ),
    MYPLUGIN_VERSION,
    array( 'in_footer' => true )
);
```

## Critical rules

- **Always enqueue the matching stylesheet** for `wp-color-picker` and `wp-pointer`. The script-only enqueue renders unstyled.
- **jQuery UI datepicker has NO default WP stylesheet**. You ship one or the picker renders as an ugly unstyled table.
- **`dateFormat` uses jQuery UI's syntax**, not PHP's. `yy-mm-dd`, not `Y-m-d`. The capitalization differs and silently produces wrong dates.
- **Add `autocomplete="off"` to datepicker / autocomplete inputs** to prevent browser-native overlays from competing with the widget.
- **Sanitize server-side regardless of the widget**. The widget is UX, not a validation layer — users can edit the value with DevTools, paste arbitrary text, or disable JS.
- **For pointers, use core's `dismiss-wp-pointer` AJAX action**, not a custom one. The user-meta key `dismissed_wp_pointers` is what every other dismissed pointer in WP uses; matching the convention means a clean uninstall (you can remove your slug from the CSV in your uninstaller).
- **Pointer slugs must be `sanitize_key()`-safe**. Use lowercase letters, numbers, and underscores, or core's dismissal handler rejects the request.
- **Don't init `wpColorPicker` while its input is inside a hidden container** — Iris reads computed dimensions at init time. Init AFTER the containing tab/accordion is shown, or call `.iris('refresh')` on the input after revealing it.
- **WordPress 7.1 bundles jQuery UI 1.14.2 with back-compat enabled.** Use public widget APIs; regression-test code that reaches into underscored methods or generated markup.

## Common AI mistakes

See `reference.md` for before/after snippets: script without stylesheet, unstyled datepicker, PHP date formats in jQuery UI, returning a Promise from autocomplete `source`, and pointer UI with no dismissal persistence.

## Cross-references

- See **`wp-admin-codemirror`** for the syntax-highlighted textarea variant — different API (`wp.codeEditor.initialize`) but same general "enqueue + init at DOM-ready" rhythm.
- See **`wp-admin-media-frame`** for the picker that lives next to these on most settings pages.
- See **`wp-admin-settings-api`** for routing the field values through `register_setting()` + `sanitize_callback`.
- See **`wp-plugin-assets-loading`** for the `$hook_suffix` gate that keeps these out of every admin page.

## What this skill does NOT cover

- React/Gutenberg form controls (`@wordpress/components` Color Picker, Date Picker, etc.). Different API stack — `<ColorPicker>` not `wpColorPicker`, lives in the block editor or a custom React island.
- Range slider, time picker, file picker. WP doesn't ship dedicated widgets for these in classic admin — for a range, an `<input type="range">` works fine; for time, the HTML5 `<input type="time">` does the job.
- Customizer color / date controls (`wp.customize.ColorControl`). Different abstraction over the same picker.

## References

- `wp-admin/js/color-picker.js:23` — `wpColorPicker` widget definition with `options` defaults.
- `wp-includes/js/wp-pointer.js:12` — `$.widget('wp.pointer', ...)` definition.
- `wp-includes/script-loader.php:1502` — `wp-color-picker` script handle registration (depends on `iris`).
- `wp-includes/script-loader.php:860` — `wp-pointer` script handle (depends on `jquery-ui-core`).
- `wp-includes/script-loader.php:937-939` — `jquery-ui-autocomplete` (deps `jquery-ui-menu`, `wp-a11y`) and `jquery-ui-datepicker` (deps `jquery-ui-core`).
- `wp-admin/js/user-suggest.js`, `wp-admin/js/tags-suggest.js` — reference autocomplete implementations for users/tags.
- `reference.md` — autocomplete source shapes, pointer dismissal example, and common mistakes.
- Official documentation: <https://developer.wordpress.org/reference/functions/wp_enqueue_script/>
- Official documentation: <https://api.jqueryui.com/datepicker/>
- Official documentation: <https://api.jqueryui.com/autocomplete/>
- Official documentation: <https://automattic.github.io/Iris/>

Files in this skill

  • SKILL.md12.7 KB
  • reference.md3.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.