PHP Web Components for WordPress
Web Components are a set of web platform APIs that allow you to create new custom, reusable, encapsulated HTML tags to use in web pages and web apps.
But what if we could build Web Components using PHP? What if we could create reusable components that work seamlessly with WordPress?
The Problem
Traditional WordPress development often leads to:
- Duplicated code across themes and plugins
- Inconsistent markup and styling
- Difficulty maintaining and updating components
- Poor separation of concerns
The Solution: PHP Web Components
PHP Web Components combine the power of PHP with the standards-based approach of Web Components. Here's how they work:
- Markup is already generated by the server
- It works similar to how Web Components do – in that JavaScript functionality is achieved by manipulating the DOM – rather than the Virtual DOM like what React and others do, which hydrate the front-end once a Server Side Rendered page is served
Key Principles
When building PHP Web Components, we follow these principles:
- Components need to exist in a single place (file). Updating the component in this file must update it everywhere it is used on the website
- Components need to be void of context: A component's only purpose is to be a container of content
- Components need to be flexible enough to work with any front-end methodology or utility-class-driven framework like Tailwind CSS
- Components need to be flexible, depending on the context or content passed into it
- Bonus: Components need to work in the Gutenberg block editor, and can be changed or updated without affecting any posts that have already used it
- Bonus: Components must handle their own CSS and JavaScript
Example: A Button Component
Let's create a simple button component:
<?php
/**
* Button Component
*/
class Button_Component {
/**
* Render the button
*
* @param array $args Component arguments.
* @return string
*/
public static function render( $args = [] ) {
$defaults = [
'text' => 'Click me',
'url' => '#',
'variant' => 'primary',
'size' => 'medium',
'disabled' => false,
];
$args = wp_parse_args( $args, $defaults );
$classes = [
'button',
'button--' . $args['variant'],
'button--' . $args['size'],
];
if ( $args['disabled'] ) {
$classes[] = 'button--disabled';
}
$class_string = implode( ' ', array_filter( $classes ) );
return sprintf(
'<a href="%s" class="%s" %s>%s</a>',
esc_url( $args['url'] ),
esc_attr( $class_string ),
$args['disabled'] ? 'aria-disabled="true"' : '',
esc_html( $args['text'] )
);
}
}
Usage
// Basic usage
echo Button_Component::render([
'text' => 'Submit',
'url' => '/submit',
]);
// With variants
echo Button_Component::render([
'text' => 'Cancel',
'url' => '/cancel',
'variant' => 'secondary',
'size' => 'large',
]);
Advanced Example: A Card Component
Here's a more complex component that demonstrates composition:
<?php
/**
* Card Component
*/
class Card_Component {
/**
* Render the card
*
* @param array $args Component arguments.
* @return string
*/
public static function render( $args = [] ) {
$defaults = [
'title' => '',
'content' => '',
'image' => '',
'image_alt' => '',
'footer' => '',
'variant' => 'default',
'clickable' => false,
'url' => '',
];
$args = wp_parse_args( $args, $defaults );
$classes = [
'card',
'card--' . $args['variant'],
];
if ( $args['clickable'] ) {
$classes[] = 'card--clickable';
}
$class_string = implode( ' ', array_filter( $classes ) );
$image_html = '';
if ( $args['image'] ) {
$image_html = sprintf(
'<img src="%s" alt="%s" class="card__image">',
esc_url( $args['image'] ),
esc_attr( $args['image_alt'] )
);
}
$footer_html = '';
if ( $args['footer'] ) {
$footer_html = sprintf(
'<div class="card__footer">%s</div>',
wp_kses_post( $args['footer'] )
);
}
$content = sprintf(
'<div class="card__content">%s</div>',
wp_kses_post( $args['content'] )
);
$card_html = sprintf(
'<div class="%s">%s%s%s</div>',
esc_attr( $class_string ),
$image_html,
$content,
$footer_html
);
if ( $args['clickable'] && $args['url'] ) {
$card_html = sprintf(
'<a href="%s" class="card__link">%s</a>',
esc_url( $args['url'] ),
$card_html
);
}
return $card_html;
}
}
Gutenberg Integration
To make components work with Gutenberg, we need to register them as blocks:
<?php
/**
* Register components as Gutenberg blocks
*/
function register_component_blocks() {
// Only register on the front-end for performance
if ( is_admin() ) {
return;
}
// Register Button Block
register_block_type( 'my-theme/button', [
'render_callback' => function( $attributes, $content ) {
return Button_Component::render( $attributes );
},
] );
// Register Card Block
register_block_type( 'my-theme/card', [
'render_callback' => function( $attributes, $content ) {
return Card_Component::render( $attributes );
},
] );
}
add_action( 'init', 'register_component_blocks' );
CSS and JavaScript
Components should handle their own styles and behavior:
<?php
/**
* Enqueue component assets
*/
function enqueue_component_assets() {
wp_enqueue_style(
'my-components',
get_template_directory_uri() . '/assets/css/components.css',
[],
'1.0.0'
);
wp_enqueue_script(
'my-components',
get_template_directory_uri() . '/assets/js/components.js',
[],
'1.0.0',
true
);
}
add_action( 'wp_enqueue_scripts', 'enqueue_component_assets' );
Benefits
- Reusability: Components can be used anywhere in your WordPress site
- Consistency: All instances of a component look and behave the same
- Maintainability: Update a component once, update it everywhere
- Standards-based: Works with web standards and accessibility guidelines
- Framework-agnostic: Works with any CSS framework or methodology
- Gutenberg-compatible: Can be used as blocks in the editor
Best Practices
- Single Responsibility: Each component should do one thing well
- Props-based: Use arguments to configure components
- Accessibility: Include proper ARIA attributes and keyboard navigation
- Documentation: Document all available props and their types
- Testing: Test components in isolation and in context
Conclusion
PHP Web Components provide a powerful way to build reusable, maintainable components in WordPress. By following web standards and PHP best practices, you can create a component library that scales with your project.
The key is to keep components simple, focused, and standards-based. This approach works well with WordPress's philosophy of simplicity and accessibility.