ACF Form is an awesome tool. It can be used in many scenarios. But when it comes to front-end display, it can become tricky to make it compatible with the actual WordPress theme. In this tutorial we’ll see how to make ACF Form use Bootstrap 4 logic.
First, we need to deregister ACF styles added by acf_form_head()
to avoid conflicts. To proceed, we will use wp_deregister_style()
.
add_action('wp_enqueue_scripts', 'acf_form_deregister_styles');
function acf_form_deregister_styles(){
// Deregister ACF Form style
wp_deregister_style('acf-global');
wp_deregister_style('acf-input');
// Avoid dependency conflict
wp_register_style('acf-global', false);
wp_register_style('acf-input', false);
}
The filter acf/validate_form
let use customize the default behavior of every acf_form()
calls. We will first check if any argument has been manually set inside acf_form()
, so we will still have the choice to bypass our Bootstrap 4 default behavior.
Using the official ACF Form documentation, we will wrap form fields with <div class="row"></div>
. On our way, we will also add alert alert-success
on the sucess message. And finally do the same for the submit button, and append btn btn-primary
classes.
add_filter('acf/validate_form', 'acf_form_bootstrap_styles');
function acf_form_bootstrap_styles($args){
// Before ACF Form
if(!$args['html_before_fields'])
$args['html_before_fields'] = '<div class="row">'; // May be .form-row
// After ACF Form
if(!$args['html_after_fields'])
$args['html_after_fields'] = '</div>';
// Success Message
if($args['html_updated_message'] == '<div id="message" class="updated"><p>%s</p></div>')
$args['html_updated_message'] = '<div id="message" class="updated alert alert-success">%s</div>';
// Submit button
if($args['html_submit_button'] == '<input type="submit" class="acf-button button button-primary button-large" value="%s" />')
$args['html_submit_button'] = '<input type="submit" class="acf-button button button-primary button-large btn btn-primary" value="%s" />';
return $args;
}
Now that our row has been set, we must add a default fallback column class: col-12
. Bootstrap adds form-group
to add some bottom margin, and of course, form-control
on each input to apply specific styles.
When it comes to modify a field before rendering, acf/prepare_field
is the best choice. We will target specifically ACF Form in the front-end via the is_admin()
conditional.
Every fields wrappers now have a default form group col-12
classes (100% of the row). The great thing is that you can add custom breakpoint column width directly in your ACF field administration. Just add col-md-6
in the wrapper input, and the following classes will be applied: form group col-12 col-md-6
(50% of the row starting at tablet breakpoint).
add_filter('acf/prepare_field', 'acf_form_fields_bootstrap_styles');
function acf_form_fields_bootstrap_styles($field){
// Target ACF Form Front only
if(is_admin() && !wp_doing_ajax())
return $field;
// Add .form-group & .col-12 fallback on fields wrappers
$field['wrapper']['class'] .= ' form-group col-12';
// Add .form-control on fields
$field['class'] .= ' form-control';
return $field;
}
text-danger
on requiredIn ACF, there is a hook for (almost) everything. Here is an example on how to add text-danger
class on the “required” asterisk:
add_filter('acf/get_field_label', 'acf_form_fields_required_bootstrap_styles');
function acf_form_fields_required_bootstrap_styles($label){
// Target ACF Form Front only
if(is_admin() && !wp_doing_ajax())
return $label;
// Add .text-danger
$label = str_replace('<span class="acf-required">*</span>', '<span class="acf-required text-danger">*</span>', $label);
return $label;
}
This step is not mandatory, but if you want to add CSS classes on error messages, you will have to add some JavaScript, as the messages are spawned there. Note that the only way to add form-control
on Date picker & Google Maps fields is also to use JavaScript.
jQuery(document).ready(function($){
// Check ACF
if(typeof acf === 'undefined')
return;
// Date picker & Google Maps compatibility
$('.acf-google-map input.search, .acf-date-picker input.input').addClass('form-control');
// Clean errors on submission
acf.addAction('validation_begin', function($form){
$form.find('.acf-error-message').remove();
});
// Add alert alert-danger & move below field
acf.addAction('invalid_field', function(field){
field.$el.find('.acf-notice.-error').addClass('alert alert-danger').insertAfter(field.$el.find('.acf-input'));
});
});
The submit button & spinner still needs some tweaks. ACF Form adds a disabled
class on the acf-button
during the form validation and submission process. This behavior is natively supported by WordPress in the backend in order to avoid click spam during submission. But not in the front-end. Let’s fix that with the CSS property pointer-events: none
.
As we deregistered ACF styles, we will also have to re-introduce the spinner gif. For compatibility, we will use the native WordPress administration spinner gif.
.acf-button.disabled{
pointer-events: none;
}
.acf-loading, .acf-spinner {
height: 20px;
width: 20px;
vertical-align: text-top;
background: transparent url(/wp-admin/images/spinner.gif) no-repeat 50% 50%;
display:none;
}
Our ACF Bootstrap Form is now ready! The HTML render exactly as excepted, using all Bootstrap 4 form components.
<form id="acf-form" class="acf-form" action="" method="post">
<div id="acf-form-data" class="acf-hidden">
<input id="_acf_screen" name="_acf_screen" value="acf_form" type="hidden">
<input id="_acf_post_id" name="_acf_post_id" value="new_post" type="hidden">
<input id="_acf_validation" name="_acf_validation" value="1" type="hidden">
<input id="_acf_form" name="_acf_form" value="..." type="hidden">
<input id="_acf_nonce" name="_acf_nonce" value="6aa82f255b" type="hidden">
<input id="_acf_changed" name="_acf_changed" value="0" type="hidden">
</div>
<div class="acf-fields acf-form-fields -top">
<!-- .row -->
<div class="row">
<!-- .form-group .col-12 -->
<div
class="acf-field acf-field-text acf-field-5ccc199a22617 is-required form-group col-12"
data-name="name"
data-type="text"
data-key="field_5ccc199a22617"
data-required="1">
<div class="acf-label">
<label for="acf-field_5ccc199a22617">
<!-- .text-danger -->
Name <span class="acf-required text-danger">*</span>
</label>
</div>
<div class="acf-input">
<div class="acf-input-wrap">
<!-- .form-control -->
<input type="text" id="acf-field_5ccc199a22617" class="form-control" name="acf[field_5ccc199a22617]" required="required">
</div>
</div>
</div>
<!-- .form-group .col-12 -->
<div
class="acf-field acf-field-email acf-field-5ccc19a022618 is-required form-group col-12"
data-name="email"
data-type="email"
data-key="field_5ccc19a022618"
data-required="1">
<div class="acf-label">
<label for="acf-field_5ccc19a022618">
<!-- .text-danger -->
E-Mail <span class="acf-required text-danger">*</span>
</label>
</div>
<div class="acf-input">
<div class="acf-input-wrap">
<!-- .form-control -->
<input type="email" id="acf-field_5ccc19a022618" class="form-control" name="acf[field_5ccc19a022618]" required="required">
</div>
</div>
</div>
<!-- .form-group .col-12 -->
<div
class="acf-field acf-field-textarea acf-field-5ccc19a922619 is-required form-group col-12"
data-name="text"
data-type="textarea"
data-key="field_5ccc19a922619"
data-required="1">
<div class="acf-label">
<label for="acf-field_5ccc19a922619">
<!-- .text-danger -->
Text <span class="acf-required text-danger">*</span>
</label>
</div>
<div class="acf-input">
<!-- .form-control -->
<textarea id="acf-field_5ccc19a922619" class="form-control" name="acf[field_5ccc19a922619]" rows="8" required="required"></textarea>
</div>
</div>
<div
class="acf-field acf-field-text acf-field--validate-email form-group col-12"
style="display:none !important;"
data-name="_validate_email"
data-type="text"
data-key="_validate_email">
<div class="acf-label">
<label for="acf-_validate_email">
Validate Email
</label>
</div>
<div class="acf-input">
<div class="acf-input-wrap">
<input type="text" id="acf-_validate_email" class="form-control" name="acf[_validate_email]">
</div>
</div>
</div>
<!-- /.row -->
</div>
</div>
<!-- Submit -->
<div class="acf-form-submit">
<!-- .btn .btn-primary -->
<input type="submit" class="acf-button button button-primary button-large btn btn-primary" value="Submit">
<span class="acf-spinner"></span>
</div>
</form>