Layout Settings in the new Gutenberg editor

For a very basic implementation of adding a text input to add body classes(would also work for adding a second field for post classes if necessary) to pages/posts, you could do the following:

  1. Register a custom field with register_post_meta – I’ll use an empty string for the first param, post_type so it applies to all. Also note that we want to make use of the REST API, so the value is usable in Gutenberg by passing true to show_in_rest:
// File: themeslug/functions.php
// Register the 'themeslug_add_body_class' post meta.
add_action( 'after_setup_theme', function() {
    register_post_meta( '', 'themeslug_add_body_class', array(
        'show_in_rest' => true,
        'single' => true,
        'type' => 'string',
    ) );
} );
  1. Add a custom panel called Layout Settings with a text input control, which is linked to the value of themeslug_add_body_class. We will add this to Document Settings in Gutenberg. To do this we will use the SlotFill called PluginDocumentSettingPanel. I’ll use the basic example from the Block Editor Handbook, so refer to the Tutorials section for a more in depth explanation of what everything in the JS code is doing.
// File: themeslug/assets/js/editor.js
( function( wp ) {
    var { registerPlugin } = wp.plugins;
    var { PluginDocumentSettingPanel } = wp.editPost;
    var el = wp.element.createElement;
    var Text = wp.components.TextControl;
    var withSelect = wp.data.withSelect;
    var withDispatch = wp.data.withDispatch;

    var mapSelectToProps = function( select ) {
        return {
            metaFieldValue: select( 'core/editor' )
                .getEditedPostAttribute( 'meta' )
                [ 'themeslug_add_body_class' ],
        }
    }

    var mapDispatchToProps = function( dispatch ) {
        return {
            setMetaFieldValue: function( value ) {
                dispatch( 'core/editor' ).editPost(
                    { meta: { themeslug_add_body_class: value } }
                );
            }
        }
    }

    var MetaBlockField = function( props ) {
        return el( Text, {
            label: 'Custom Body Classes',
            value: props.metaFieldValue,
            onChange: function( content ) {
                props.setMetaFieldValue( content );
            },
        } );
    }

    var MetaBlockFieldWithData = withSelect( mapSelectToProps )( MetaBlockField );
    var MetaBlockFieldWithDataAndActions = withDispatch( mapDispatchToProps )( MetaBlockFieldWithData );

    registerPlugin( 'theme-slug-add-body-class', {
        render: function() {
            return el( PluginDocumentSettingPanel,
                {
                    name: 'theme-slug-add-body-class',
                    title: 'Layout Settings',
                    className: "theme-slug-custom-body-class",
                    icon: '',
                },
                el( 'div',
                    { className: 'theme-slug-add-body-class-content' },
                    el( MetaBlockFieldWithDataAndActions )
                )
            );
        }
    } );
} )( window.wp );
  1. Enqueue the JS script – to do this we will hook into enqueue_block_editor_assets, and use wp_enqueue_script like any other JS file. For script deps I’ll just list them manually for simplicity:
// File: themeslug/functions.php
// Enqueue the JS for gutenberg.
add_action( 'enqueue_block_editor_assets', function() {
    wp_enqueue_script(
        'themeslug-editor-add-body-class',
        get_theme_file_uri( '/assets/js/editor.js' ),
        [ 'wp-plugins', 'wp-data', 'wp-components', 'wp-edit-post', 'wp-element', 'wp-compose' ],
        '1.0.0',
        true
    );
} );
  1. Now we have the meta registered, and our custom control/script enqueued and loaded in Gutenberg, so when visiting the editor we should see the Layout Settings panel added to Document Settings with our text field added under it:

enter image description here

The last part then would be to filter body_class with the user’s input that is saved in the meta:

// File: themeslug/functions.php
// Dynamically add body class based on post meta saved.
add_filter( 'body_class', function( $classes ) {
    $body_classes = get_post_meta( get_the_ID(), 'themeslug_add_body_class', true );
    if ( '' !== $body_classes ) {
        $body_classes = array_map( 'esc_attr', explode( ' ', $body_classes ) );
        $classes = array_merge( $classes, $body_classes );
    }
    return $classes;
} );

This allows for multiple classes added to body, which are space separated, but you can handle logic however your want for that obviously. You can extend this to add a second field for custom classes to be added as well. Instead of filtering body_class you could use post_class for that.