diff --git a/src/Utility/ArrayObject.php b/src/Utility/ArrayObject.php
new file mode 100644
index 0000000000000000000000000000000000000000..b5d448ca107cdc3168f87c0da118f633013b23f8
--- /dev/null
+++ b/src/Utility/ArrayObject.php
@@ -0,0 +1,413 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\ui_suite_bootstrap\Utility;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
+use Drupal\Core\Render\AttachmentsInterface;
+use Drupal\Core\Render\BubbleableMetadata;
+
+/**
+ * Custom ArrayObject implementation.
+ *
+ * The native ArrayObject is unnecessarily complicated.
+ *
+ * @ingroup utility
+ */
+class ArrayObject implements \IteratorAggregate, \ArrayAccess, \Serializable, \Countable, AttachmentsInterface, RefinableCacheableDependencyInterface {
+
+  /**
+   * The array.
+   *
+   * @var array
+   */
+  protected $array;
+
+  /**
+   * Array object constructor.
+   *
+   * @param array $array
+   *   An array.
+   */
+  public function __construct(array $array = []) {
+    $this->array = $array;
+  }
+
+  /**
+   * Returns whether the requested key exists.
+   *
+   * @param mixed $key
+   *   A key.
+   *
+   * @return bool
+   *   TRUE or FALSE
+   */
+  public function __isset($key) {
+    return $this->offsetExists($key);
+  }
+
+  /**
+   * Sets the value at the specified key to value.
+   *
+   * @param mixed $key
+   *   A key.
+   * @param mixed $value
+   *   A value.
+   */
+  public function __set($key, $value) {
+    $this->offsetSet($key, $value);
+  }
+
+  /**
+   * Unsets the value at the specified key.
+   *
+   * @param mixed $key
+   *   A key.
+   */
+  public function __unset($key) {
+    $this->offsetUnset($key);
+  }
+
+  /**
+   * Returns the value at the specified key by reference.
+   *
+   * @param mixed $key
+   *   A key.
+   *
+   * @return mixed
+   *   The stored value.
+   */
+  public function &__get($key) {
+    $ret = &$this->offsetGet($key);
+    return $ret;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addAttachments(array $attachments) {
+    BubbleableMetadata::createFromRenderArray($this->array)->addAttachments($attachments)->applyTo($this->array);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addCacheContexts(array $cache_contexts) {
+    BubbleableMetadata::createFromRenderArray($this->array)->addCacheContexts($cache_contexts)->applyTo($this->array);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addCacheTags(array $cache_tags) {
+    BubbleableMetadata::createFromRenderArray($this->array)->addCacheTags($cache_tags)->applyTo($this->array);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addCacheableDependency($other_object) {
+    BubbleableMetadata::createFromRenderArray($this->array)->addCacheableDependency($other_object)->applyTo($this->array);
+    return $this;
+  }
+
+  /**
+   * Appends the value.
+   *
+   * @param mixed $value
+   *   A value.
+   */
+  public function append($value): void {
+    $this->array[] = $value;
+  }
+
+  /**
+   * Sort the entries by value.
+   */
+  public function asort(): void {
+    \asort($this->array);
+  }
+
+  /**
+   * Merges an object's cacheable metadata into the variables array.
+   *
+   * @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $object
+   *   The object whose cacheability metadata to retrieve. If it implements
+   *   CacheableDependencyInterface, its cacheability metadata will be used,
+   *   otherwise, the passed in object must be assumed to be uncacheable, so
+   *   max-age 0 is set.
+   *
+   * @return $this
+   */
+  public function bubbleObject($object) {
+    BubbleableMetadata::createFromRenderArray($this->array)->merge(BubbleableMetadata::createFromObject($object))->applyTo($this->array);
+    return $this;
+  }
+
+  /**
+   * Merges a render array's cacheable metadata into the variables array.
+   *
+   * @param array $build
+   *   A render array.
+   *
+   * @return $this
+   */
+  public function bubbleRenderArray(array $build) {
+    BubbleableMetadata::createFromRenderArray($this->array)->merge(BubbleableMetadata::createFromRenderArray($build))->applyTo($this->array);
+    return $this;
+  }
+
+  /**
+   * Get the number of public properties in the ArrayObject.
+   *
+   * @return int
+   *   The count.
+   */
+  public function count(): int {
+    return \count($this->array);
+  }
+
+  /**
+   * Exchange the array for another one.
+   *
+   * @param array|ArrayObject $data
+   *   New data.
+   *
+   * @throws \InvalidArgumentException
+   *   When the passed data is not an array or an instance of ArrayObject.
+   *
+   * @return array
+   *   The old array.
+   */
+  public function exchangeArray($data) {
+    if (!\is_array($data) && \is_object($data) && !($data instanceof ArrayObject)) {
+      throw new \InvalidArgumentException('Passed variable is not an array or an instance of \Drupal\bootstrap\Utility\ArrayObject.');
+    }
+    if (\is_object($data) && $data instanceof ArrayObject) {
+      $data = $data->getArrayCopy();
+    }
+    $old = $this->array;
+    $this->array = $data;
+    return $old;
+  }
+
+  /**
+   * Creates a copy of the ArrayObject.
+   *
+   * @return array
+   *   A copy of the array.
+   */
+  public function getArrayCopy() {
+    return $this->array;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAttachments() {
+    return BubbleableMetadata::createFromRenderArray($this->array)->getAttachments();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts() {
+    return BubbleableMetadata::createFromRenderArray($this->array)->getCacheContexts();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags() {
+    return BubbleableMetadata::createFromRenderArray($this->array)->getCacheTags();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheMaxAge() {
+    return BubbleableMetadata::createFromRenderArray($this->array)->getCacheMaxAge();
+  }
+
+  /**
+   * Creates a new iterator from an ArrayObject instance.
+   *
+   * @return \ArrayIterator
+   *   An array iterator.
+   */
+  public function getIterator(): \Traversable {
+    return new \ArrayIterator($this->array);
+  }
+
+  /**
+   * Sort the entries by key.
+   */
+  public function ksort(): void {
+    \ksort($this->array);
+  }
+
+  /**
+   * Merges multiple values into the array.
+   *
+   * @param array $values
+   *   An associative key/value array.
+   * @param bool $recursive
+   *   Flag determining whether or not to recursively merge key/value pairs.
+   *
+   * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
+   */
+  public function merge(array $values, $recursive = TRUE): void {
+    if ($recursive) {
+      $this->array = NestedArray::mergeDeepArray([$this->array, $values], TRUE);
+    }
+    else {
+      $this->array += $values;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mergeCacheMaxAge($max_age) {
+    BubbleableMetadata::createFromRenderArray($this->array)->mergeCacheMaxAge($max_age)->applyTo($this->array);
+    return $this;
+  }
+
+  /**
+   * Sort an array using a case insensitive "natural order" algorithm.
+   */
+  public function natcasesort(): void {
+    \natcasesort($this->array);
+  }
+
+  /**
+   * Sort entries using a "natural order" algorithm.
+   */
+  public function natsort(): void {
+    \natsort($this->array);
+  }
+
+  /**
+   * Returns whether the requested key exists.
+   *
+   * @param mixed $key
+   *   A key.
+   *
+   * @return bool
+   *   TRUE or FALSE
+   */
+  public function offsetExists($key): bool {
+    return isset($this->array[$key]);
+  }
+
+  /**
+   * Returns the value at the specified key.
+   *
+   * @param mixed $key
+   *   A key.
+   * @param mixed $default
+   *   The default value to set if $key does not exist.
+   *
+   * @return mixed
+   *   The value.
+   */
+  public function &offsetGet($key, $default = NULL): mixed {
+    if (!$this->offsetExists($key)) {
+      $this->array[$key] = $default;
+    }
+    $ret = &$this->array[$key];
+    return $ret;
+  }
+
+  /**
+   * Sets the value at the specified key to value.
+   *
+   * @param mixed $key
+   *   A key.
+   * @param mixed $value
+   *   A value.
+   */
+  public function offsetSet($key, $value): void {
+    $this->array[$key] = $value;
+  }
+
+  /**
+   * Unsets the value at the specified key.
+   *
+   * @param mixed $key
+   *   A key.
+   */
+  public function offsetUnset($key): void {
+    if ($this->offsetExists($key)) {
+      unset($this->array[$key]);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setAttachments(array $attachments) {
+    BubbleableMetadata::createFromRenderArray($this->array)->setAttachments($attachments)->applyTo($this->array);
+    return $this;
+  }
+
+  /**
+   * Sort entries with a user-defined function and maintain key association.
+   *
+   * @param mixed $function
+   *   A callable function.
+   */
+  public function uasort($function): void {
+    if (\is_callable($function)) {
+      \uasort($this->array, $function);
+    }
+  }
+
+  /**
+   * Sort the entries by keys using a user-defined comparison function.
+   *
+   * @param mixed $function
+   *   A callable function.
+   */
+  public function uksort($function): void {
+    if (\is_callable($function)) {
+      \uksort($this->array, $function);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function serialize() {
+    return \serialize($this->__serialize());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function unserialize($serialized) {
+    // phpcs:disable
+    $this->__unserialize(\unserialize($serialized));
+    // phpcs:enable
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __serialize(): array {
+    return \get_object_vars($this);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __unserialize(array $data): void {
+    $this->exchangeArray($data['array']);
+  }
+
+}
diff --git a/src/Utility/Attributes.php b/src/Utility/Attributes.php
new file mode 100644
index 0000000000000000000000000000000000000000..7127c2e632679ed644999cff52dd8684716a067d
--- /dev/null
+++ b/src/Utility/Attributes.php
@@ -0,0 +1,192 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\ui_suite_bootstrap\Utility;
+
+use Drupal\Core\Template\AttributeValueBase;
+
+/**
+ * Class to help modify attributes.
+ *
+ * @ingroup utility
+ */
+class Attributes extends ArrayObject {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array &$array = []) {
+    $this->array = &$array;
+  }
+
+  /**
+   * Add class(es) to the array.
+   *
+   * @param string|array $class
+   *   An individual class or an array of classes to add.
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\Attributes::getClasses()
+   */
+  public function addClass($class): void {
+    // Handle core Attribute based object values.
+    // @see https://www.drupal.org/project/bootstrap/issues/3020266
+    if ($class instanceof AttributeValueBase) {
+      $class = $class->value();
+    }
+    $classes = &$this->getClasses();
+    $classes = \array_unique(\array_merge($classes, (array) $class));
+  }
+
+  /**
+   * Retrieve a specific attribute from the array.
+   *
+   * @param string $name
+   *   The specific attribute to retrieve.
+   * @param mixed $default
+   *   (optional) The default value to set if the attribute does not exist.
+   *
+   * @return mixed
+   *   A specific attribute value, passed by reference.
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\ArrayObject::offsetGet()
+   */
+  public function &getAttribute($name, $default = NULL) {
+    return $this->offsetGet($name, $default);
+  }
+
+  /**
+   * Retrieves classes from the array.
+   *
+   * @return array
+   *   The classes array, passed by reference.
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\ArrayObject::offsetGet()
+   */
+  public function &getClasses(): array {
+    $classes = &$this->offsetGet('class', []);
+    $classes = \array_unique($classes);
+    return $classes;
+  }
+
+  /**
+   * Indicates whether a specific attribute is set.
+   *
+   * @param string $name
+   *   The attribute to search for.
+   *
+   * @return bool
+   *   TRUE or FALSE.
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\ArrayObject::offsetExists()
+   */
+  public function hasAttribute($name): bool {
+    return $this->offsetExists($name);
+  }
+
+  /**
+   * Indicates whether a class is present in the array.
+   *
+   * @param string|array $class
+   *   The class or array of classes to search for.
+   * @param bool $all
+   *   Flag determining to check if all classes are present.
+   *
+   * @return bool
+   *   TRUE or FALSE.
+   *
+   * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\Attributes::getClasses()
+   */
+  public function hasClass($class, $all = FALSE): bool {
+    $classes = (array) $class;
+    $result = \array_intersect($classes, $this->getClasses());
+    return $all ? $result && \count($classes) === \count($result) : (bool) $result;
+  }
+
+  /**
+   * Removes an attribute from the array.
+   *
+   * @param string|array $name
+   *   The name of the attribute to remove.
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\ArrayObject::offsetUnset()
+   */
+  public function removeAttribute($name): void {
+    $this->offsetUnset($name);
+  }
+
+  /**
+   * Removes a class from the attributes array.
+   *
+   * @param string|array $class
+   *   An individual class or an array of classes to remove.
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\Attributes::getClasses()
+   */
+  public function removeClass($class): void {
+    $classes = &$this->getClasses();
+    $classes = \array_values(\array_diff($classes, (array) $class));
+  }
+
+  /**
+   * Replaces a class in the attributes array.
+   *
+   * @param string $old
+   *   The old class to remove.
+   * @param string $new
+   *   The new class. It will not be added if the $old class does not exist.
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\Attributes::getClasses()
+   */
+  public function replaceClass($old, $new): void {
+    $classes = &$this->getClasses();
+    $key = \array_search($old, $classes, TRUE);
+    if ($key !== FALSE) {
+      $classes[$key] = $new;
+    }
+  }
+
+  /**
+   * Sets an attribute on the array.
+   *
+   * @param string $name
+   *   The name of the attribute to set.
+   * @param mixed $value
+   *   The value of the attribute to set.
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\ArrayObject::offsetSet()
+   */
+  public function setAttribute($name, $value): void {
+    // Handle class attribute differently.
+    if ($name === 'class') {
+      $this->removeAttribute('class');
+      $this->addClass($value);
+    }
+    else {
+      $this->offsetSet($name, $value);
+    }
+  }
+
+  /**
+   * Sets multiple attributes on the array.
+   *
+   * @param array $values
+   *   An associative key/value array of attributes to set.
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\ArrayObject::merge()
+   */
+  public function setAttributes(array $values): void {
+    // Handle class attribute differently.
+    $classes = $values['class'] ?? [];
+    unset($values['class']);
+    if ($classes) {
+      $this->addClass($classes);
+    }
+
+    // Merge the reset of the attributes.
+    $this->merge($values);
+  }
+
+}
diff --git a/src/Utility/DrupalAttributes.php b/src/Utility/DrupalAttributes.php
new file mode 100644
index 0000000000000000000000000000000000000000..a8601d8659f6fdee4d849ea0607da910e4c805a8
--- /dev/null
+++ b/src/Utility/DrupalAttributes.php
@@ -0,0 +1,294 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\ui_suite_bootstrap\Utility;
+
+use Drupal\Core\Template\Attribute;
+
+/**
+ * Class for managing multiple types of attributes commonly found in Drupal.
+ */
+class DrupalAttributes extends ArrayObject {
+
+  /**
+   * Defines the "attributes" storage type constant.
+   *
+   * @var string
+   */
+  public const ATTRIBUTES = 'attributes';
+
+  /**
+   * Defines the "description_attributes" storage type constant.
+   *
+   * @var string
+   */
+  public const DESCRIPTION = 'description_attributes';
+
+  /**
+   * Defines the "input_group_attributes" storage type constant.
+   *
+   * @var string
+   */
+  public const INPUT_GROUP = 'input_group_attributes';
+
+  /**
+   * Defines the "label_attributes" storage type constant.
+   *
+   * @var string
+   */
+  public const LABEL = 'label_attributes';
+
+  /**
+   * Defines the "title_attributes" storage type constant.
+   *
+   * @var string
+   */
+  public const TITLE = 'title_attributes';
+
+  /**
+   * Defines the "wrapper_attributes" storage type constant.
+   *
+   * @var string
+   */
+  public const WRAPPER = 'wrapper_attributes';
+
+  /**
+   * Stored attribute instances.
+   *
+   * @var \Drupal\ui_suite_bootstrap\Utility\Attributes[]
+   */
+  protected $attributes = [];
+
+  /**
+   * A prefix to use for retrieving attribute keys from the array.
+   *
+   * @var string
+   */
+  protected $attributePrefix = '';
+
+  /**
+   * Add class(es) to an attributes object.
+   *
+   * This is a wrapper method to retrieve the correct attributes storage object
+   * and then add the class(es) to it.
+   *
+   * @param string|array $class
+   *   An individual class or an array of classes to add.
+   * @param string $type
+   *   (optional) The type of attributes to use for this method.
+   *
+   * @return $this
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\Attributes::addClass()
+   */
+  public function addClass($class, $type = DrupalAttributes::ATTRIBUTES) {
+    $this->getAttributes($type)->addClass($class);
+    return $this;
+  }
+
+  /**
+   * Retrieve a specific attribute from an attributes object.
+   *
+   * This is a wrapper method to retrieve the correct attributes storage object
+   * and then retrieve the attribute from it.
+   *
+   * @param string $name
+   *   The specific attribute to retrieve.
+   * @param mixed $default
+   *   (optional) The default value to set if the attribute does not exist.
+   * @param string $type
+   *   (optional) The type of attributes to use for this method.
+   *
+   * @return mixed
+   *   A specific attribute value, passed by reference.
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\Attributes::getAttribute()
+   */
+  public function &getAttribute($name, $default = NULL, $type = DrupalAttributes::ATTRIBUTES) {
+    return $this->getAttributes($type)->getAttribute($name, $default);
+  }
+
+  /**
+   * Retrieves a specific attributes object.
+   *
+   * @param string $type
+   *   (optional) The type of attributes to use for this method.
+   *
+   * @return \Drupal\ui_suite_bootstrap\Utility\Attributes
+   *   An attributes object for $type.
+   */
+  public function getAttributes($type = DrupalAttributes::ATTRIBUTES) {
+    if (!isset($this->attributes[$type])) {
+      $attributes = &$this->offsetGet($this->attributePrefix . $type, []);
+      if ($attributes instanceof Attribute) {
+        $attributes = $attributes->toArray();
+      }
+      $this->attributes[$type] = new Attributes($attributes);
+    }
+    return $this->attributes[$type];
+  }
+
+  /**
+   * Retrieves classes from an attributes object.
+   *
+   * This is a wrapper method to retrieve the correct attributes storage object
+   * and then retrieve the set classes from it.
+   *
+   * @param string $type
+   *   (optional) The type of attributes to use for this method.
+   *
+   * @return array
+   *   The classes array, passed by reference.
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\Attributes::getClasses()
+   */
+  public function &getClasses($type = DrupalAttributes::ATTRIBUTES) {
+    return $this->getAttributes($type)->getClasses();
+  }
+
+  /**
+   * Indicates whether an attributes object has a specific attribute set.
+   *
+   * This is a wrapper method to retrieve the correct attributes storage object
+   * and then check there if the attribute exists.
+   *
+   * @param string $name
+   *   The attribute to search for.
+   * @param string $type
+   *   (optional) The type of attributes to use for this method.
+   *
+   * @return bool
+   *   TRUE or FALSE
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\Attributes::hasAttribute()
+   */
+  public function hasAttribute($name, $type = DrupalAttributes::ATTRIBUTES) {
+    return $this->getAttributes($type)->hasAttribute($name);
+  }
+
+  /**
+   * Indicates whether an attributes object has a specific class.
+   *
+   * This is a wrapper method to retrieve the correct attributes storage object
+   * and then check there if a class exists in the attributes object.
+   *
+   * @param string $class
+   *   The class to search for.
+   * @param string $type
+   *   (optional) The type of attributes to use for this method.
+   *
+   * @return bool
+   *   TRUE or FALSE
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\Attributes::hasClass()
+   */
+  public function hasClass($class, $type = DrupalAttributes::ATTRIBUTES) {
+    return $this->getAttributes($type)->hasClass($class);
+  }
+
+  /**
+   * Removes an attribute from an attributes object.
+   *
+   * This is a wrapper method to retrieve the correct attributes storage object
+   * and then remove an attribute from it.
+   *
+   * @param string|array $name
+   *   The name of the attribute to remove.
+   * @param string $type
+   *   (optional) The type of attributes to use for this method.
+   *
+   * @return $this
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\Attributes::removeAttribute()
+   */
+  public function removeAttribute($name, $type = DrupalAttributes::ATTRIBUTES) {
+    $this->getAttributes($type)->removeAttribute($name);
+    return $this;
+  }
+
+  /**
+   * Removes a class from an attributes object.
+   *
+   * This is a wrapper method to retrieve the correct attributes storage object
+   * and then remove the class(es) from it.
+   *
+   * @param string|array $class
+   *   An individual class or an array of classes to remove.
+   * @param string $type
+   *   (optional) The type of attributes to use for this method.
+   *
+   * @return $this
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\Attributes::removeClass()
+   */
+  public function removeClass($class, $type = DrupalAttributes::ATTRIBUTES) {
+    $this->getAttributes($type)->removeClass($class);
+    return $this;
+  }
+
+  /**
+   * Replaces a class in an attributes object.
+   *
+   * This is a wrapper method to retrieve the correct attributes storage object
+   * and then replace the class(es) in it.
+   *
+   * @param string $old
+   *   The old class to remove.
+   * @param string $new
+   *   The new class. It will not be added if the $old class does not exist.
+   * @param string $type
+   *   (optional) The type of attributes to use for this method.
+   *
+   * @return $this
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\Attributes::replaceClass()
+   */
+  public function replaceClass($old, $new, $type = DrupalAttributes::ATTRIBUTES) {
+    $this->getAttributes($type)->replaceClass($old, $new);
+    return $this;
+  }
+
+  /**
+   * Sets an attribute on an attributes object.
+   *
+   * This is a wrapper method to retrieve the correct attributes storage object
+   * and then set an attribute on it.
+   *
+   * @param string $name
+   *   The name of the attribute to set.
+   * @param mixed $value
+   *   The value of the attribute to set.
+   * @param string $type
+   *   (optional) The type of attributes to use for this method.
+   *
+   * @return $this
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\Attributes::setAttribute()
+   */
+  public function setAttribute($name, $value, $type = DrupalAttributes::ATTRIBUTES) {
+    $this->getAttributes($type)->setAttribute($name, $value);
+    return $this;
+  }
+
+  /**
+   * Sets multiple attributes on an attributes object.
+   *
+   * This is a wrapper method to retrieve the correct attributes storage object
+   * and then merge multiple attributes into it.
+   *
+   * @param array $values
+   *   An associative key/value array of attributes to set.
+   * @param string $type
+   *   (optional) The type of attributes to use for this method.
+   *
+   * @return $this
+   *
+   * @see \Drupal\ui_suite_bootstrap\Utility\Attributes::setAttributes()
+   */
+  public function setAttributes(array $values, $type = DrupalAttributes::ATTRIBUTES) {
+    $this->getAttributes($type)->setAttributes($values);
+    return $this;
+  }
+
+}
diff --git a/src/Utility/Element.php b/src/Utility/Element.php
new file mode 100644
index 0000000000000000000000000000000000000000..d65afd61cb9e6fe2999eb58b8cbd36a08c541b10
--- /dev/null
+++ b/src/Utility/Element.php
@@ -0,0 +1,574 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\ui_suite_bootstrap\Utility;
+
+use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Component\Render\MarkupInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element as CoreElement;
+
+/**
+ * Provides helper methods for Drupal render elements.
+ *
+ * @see \Drupal\Core\Render\Element
+ */
+class Element extends DrupalAttributes {
+
+  /**
+   * The current state of the form.
+   *
+   * @var \Drupal\Core\Form\FormStateInterface|null
+   */
+  protected $formState;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $attributePrefix = '#';
+
+  /**
+   * Element constructor.
+   *
+   * @param array|string $element
+   *   A render array element.
+   * @param \Drupal\Core\Form\FormStateInterface|null $form_state
+   *   The current state of the form.
+   */
+  public function __construct(&$element = [], ?FormStateInterface $form_state = NULL) {
+    if (!\is_array($element)) {
+      $element = [
+        '#markup' => $element instanceof MarkupInterface ? $element : new FormattableMarkup($element, []),
+      ];
+    }
+    $this->array = &$element;
+    $this->formState = $form_state;
+  }
+
+  /**
+   * Magic get method.
+   *
+   * This is only for child elements, not properties.
+   *
+   * @param string $key
+   *   The name of the child element to retrieve.
+   *
+   * @throws \InvalidArgumentException
+   *   Throws this error when the name is a property (key starting with #).
+   *
+   * @return \Drupal\ui_suite_bootstrap\Utility\Element
+   *   The child element object.
+   */
+  public function &__get($key) {
+    if (CoreElement::property($key)) {
+      throw new \InvalidArgumentException('Cannot dynamically retrieve element property. Please use  \Drupal\ui_suite_bootstrap\Utility\Element::getProperty instead.');
+    }
+    return new self($this->offsetGet($key, []), $this->formState);
+  }
+
+  /**
+   * Magic set method.
+   *
+   * This is only for child elements, not properties.
+   *
+   * @param string $key
+   *   The name of the child element to set.
+   * @param mixed $value
+   *   The value of $name to set.
+   *
+   * @throws \InvalidArgumentException
+   *   Throws this error when the name is a property (key starting with #).
+   */
+  public function __set($key, $value) {
+    if (CoreElement::property($key)) {
+      throw new \InvalidArgumentException('Cannot dynamically retrieve element property. Use  \Drupal\ui_suite_bootstrap\Utility\Element::setProperty instead.');
+    }
+    $this->offsetSet($key, $value instanceof Element ? $value->getArray() : $value);
+  }
+
+  /**
+   * Magic isset method.
+   *
+   * This is only for child elements, not properties.
+   *
+   * @param string $name
+   *   The name of the child element to check.
+   *
+   * @throws \InvalidArgumentException
+   *   Throws this error when the name is a property (key starting with #).
+   *
+   * @return bool
+   *   TRUE or FALSE
+   */
+  public function __isset($name) {
+    if (CoreElement::property($name)) {
+      throw new \InvalidArgumentException('Cannot dynamically check if an element has a property. Use  \Drupal\ui_suite_bootstrap\Utility\Element::unsetProperty instead.');
+    }
+    return parent::__isset($name);
+  }
+
+  /**
+   * Magic unset method.
+   *
+   * This is only for child elements, not properties.
+   *
+   * @param mixed $name
+   *   The name of the child element to unset.
+   *
+   * @throws \InvalidArgumentException
+   *   Throws this error when the name is a property (key starting with #).
+   */
+  public function __unset($name) {
+    if (CoreElement::property($name)) {
+      throw new \InvalidArgumentException('Cannot dynamically unset an element property. Use  \Drupal\ui_suite_bootstrap\Utility\Element::hasProperty instead.');
+    }
+    parent::__unset($name);
+  }
+
+  /**
+   * Sets the #access property on an element.
+   *
+   * @param bool|\Drupal\Core\Access\AccessResultInterface $access
+   *   The value to assign to #access.
+   *
+   * @return static
+   */
+  public function access($access = NULL) {
+    return $this->setProperty('access', $access);
+  }
+
+  /**
+   * Appends a property with a value.
+   *
+   * @param string $name
+   *   The name of the property to set.
+   * @param mixed $value
+   *   The value of the property to set.
+   *
+   * @return static
+   */
+  public function appendProperty($name, $value) {
+    $property = &$this->getProperty($name);
+    $value = $value instanceof Element ? $value->getArray() : $value;
+
+    // If property isn't set, just set it.
+    if (!isset($property)) {
+      $property = $value;
+      return $this;
+    }
+
+    if (\is_array($property)) {
+      $property[] = Element::create($value)->getArray();
+    }
+    else {
+      $property .= (string) $value;
+    }
+
+    return $this;
+  }
+
+  /**
+   * Identifies the children of an element array, optionally sorted by weight.
+   *
+   * The children of a element array are those key/value pairs whose key does
+   * not start with a '#'. See drupal_render() for details.
+   *
+   * @param bool $sort
+   *   Boolean to indicate whether the children should be sorted by weight.
+   *
+   * @return array
+   *   The array keys of the element's children.
+   *
+   * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
+   */
+  public function childKeys($sort = FALSE) {
+    return CoreElement::children($this->array, $sort);
+  }
+
+  /**
+   * Retrieves the children of an element array, optionally sorted by weight.
+   *
+   * The children of a element array are those key/value pairs whose key does
+   * not start with a '#'. See drupal_render() for details.
+   *
+   * @param bool $sort
+   *   Boolean to indicate whether the children should be sorted by weight.
+   *
+   * @return \Drupal\ui_suite_bootstrap\Utility\Element[]
+   *   An array child elements.
+   *
+   * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
+   */
+  public function children($sort = FALSE) {
+    $children = [];
+    foreach ($this->childKeys($sort) as $child) {
+      $children[$child] = new self($this->array[$child]);
+    }
+    return $children;
+  }
+
+  /**
+   * Creates a new \Drupal\ui_suite_bootstrap\Utility\Element instance.
+   *
+   * @param array|string $element
+   *   A render array element or a string.
+   * @param \Drupal\Core\Form\FormStateInterface|null $form_state
+   *   A current FormState instance, if any.
+   *
+   * @return \Drupal\ui_suite_bootstrap\Utility\Element
+   *   The newly created element instance.
+   */
+  public static function create(&$element = [], ?FormStateInterface $form_state = NULL) {
+    return $element instanceof self ? $element : new self($element, $form_state);
+  }
+
+  /**
+   * Traverses the element to find the closest button.
+   *
+   * @return \Drupal\ui_suite_bootstrap\Utility\Element|false
+   *   The first button element or FALSE if no button could be found.
+   */
+  public function &findButton() {
+    $button = FALSE;
+    foreach ($this->children() as $child) {
+      if ($child->isButton()) {
+        $button = $child;
+        break;
+      }
+      $result = &$child->findButton();
+      if ($result) {
+        $button = $result;
+        break;
+      }
+    }
+    return $button;
+  }
+
+  /**
+   * Retrieves the render array for the element.
+   *
+   * @return array
+   *   The element render array, passed by reference.
+   */
+  public function &getArray() {
+    return $this->array;
+  }
+
+  /**
+   * Retrieves a context value from the #context element property, if any.
+   *
+   * @param string $name
+   *   The name of the context key to retrieve.
+   * @param mixed $default
+   *   Optional. The default value to use if the context $name isn't set.
+   *
+   * @return mixed|null
+   *   The context value or the $default value if not set.
+   */
+  public function &getContext($name, $default = NULL) {
+    $context = &$this->getProperty('context', []);
+    if (!isset($context[$name])) {
+      $context[$name] = $default;
+    }
+    return $context[$name];
+  }
+
+  /**
+   * Returns the error message filed against the given form element.
+   *
+   * Form errors higher up in the form structure override deeper errors as well
+   * as errors on the element itself.
+   *
+   * @throws \BadMethodCallException
+   *   When the element instance was not constructed with a valid form state
+   *   object.
+   *
+   * @return string|null
+   *   Either the error message for this element or NULL if there are no errors.
+   */
+  public function getError() {
+    if (!$this->formState) {
+      throw new \BadMethodCallException('The element instance must be constructed with a valid form state object to use this method.');
+    }
+    return $this->formState->getError($this->array);
+  }
+
+  /**
+   * Retrieves the render array for the element.
+   *
+   * @param string $name
+   *   The name of the element property to retrieve, not including the # prefix.
+   * @param mixed $default
+   *   The default to set if property does not exist.
+   *
+   * @return mixed
+   *   The property value, NULL if not set.
+   */
+  public function &getProperty($name, $default = NULL) {
+    return $this->offsetGet("#{$name}", $default);
+  }
+
+  /**
+   * Returns the visible children of an element.
+   *
+   * @return array
+   *   The array keys of the element's visible children.
+   */
+  public function getVisibleChildren() {
+    return CoreElement::getVisibleChildren($this->array);
+  }
+
+  /**
+   * Indicates whether the element has an error set.
+   *
+   * @throws \BadMethodCallException
+   *   When the element instance was not constructed with a valid form state
+   *   object.
+   *
+   * @return bool
+   *   TRUE if has error.
+   */
+  public function hasError(): bool {
+    $error = $this->getError();
+    return isset($error);
+  }
+
+  /**
+   * Indicates whether the element has a specific property.
+   *
+   * @param string $name
+   *   The property to check.
+   *
+   * @return bool
+   *   TRUE if has property.
+   */
+  public function hasProperty($name): bool {
+    return $this->offsetExists("#{$name}");
+  }
+
+  /**
+   * Indicates whether the element is a button.
+   *
+   * @return bool
+   *   TRUE or FALSE.
+   */
+  public function isButton() {
+    $button_types = ['button', 'submit', 'reset', 'image_button'];
+    return !empty($this->array['#is_button']) || $this->isType($button_types) || $this->hasClass('btn');
+  }
+
+  /**
+   * Indicates whether the given element is empty.
+   *
+   * An element that only has #cache set is considered empty, because it will
+   * render to the empty string.
+   *
+   * @return bool
+   *   Whether the given element is empty.
+   */
+  public function isEmpty() {
+    return CoreElement::isEmpty($this->array);
+  }
+
+  /**
+   * Indicates whether a property on the element is empty.
+   *
+   * @param string $name
+   *   The property to check.
+   *
+   * @return bool
+   *   Whether the given property on the element is empty.
+   */
+  public function isPropertyEmpty($name) {
+    return $this->hasProperty($name) && empty($this->getProperty($name));
+  }
+
+  /**
+   * Checks if a value is a render array.
+   *
+   * @param mixed $value
+   *   The value to check.
+   *
+   * @return bool
+   *   TRUE if the given value is a render array, otherwise FALSE.
+   */
+  public static function isRenderArray($value) {
+    return \is_array($value) && (isset($value['#type'])
+      || isset($value['#theme']) || isset($value['#theme_wrappers'])
+      || isset($value['#markup']) || isset($value['#attached'])
+      || isset($value['#cache']) || isset($value['#lazy_builder'])
+      || isset($value['#create_placeholder']) || isset($value['#pre_render'])
+      || isset($value['#post_render']) || isset($value['#process']));
+  }
+
+  /**
+   * Checks if the element is a specific type of element.
+   *
+   * @param string|array $type
+   *   The element type(s) to check.
+   *
+   * @return bool
+   *   TRUE if element is or one of $type.
+   */
+  public function isType($type) {
+    $property = $this->getProperty('type');
+    return $property && \in_array($property, (\is_array($type) ? $type : [$type]), TRUE);
+  }
+
+  /**
+   * Determines if an element is visible.
+   *
+   * @return bool
+   *   TRUE if the element is visible, otherwise FALSE.
+   */
+  public function isVisible() {
+    return CoreElement::isVisibleElement($this->array);
+  }
+
+  /**
+   * Maps an element's properties to its attributes array.
+   *
+   * @param array $map
+   *   An associative array whose keys are element property names and whose
+   *   values are the HTML attribute names to set on the corresponding
+   *   property; e.g., array('#property_name' => 'attribute_name'). If both
+   *   names are identical except for the leading '#', then an attribute name
+   *   value is sufficient and no property name needs to be specified.
+   *
+   * @return static
+   */
+  public function map(array $map) {
+    CoreElement::setAttributes($this->array, $map);
+    return $this;
+  }
+
+  /**
+   * Prepends a property with a value.
+   *
+   * @param string $name
+   *   The name of the property to set.
+   * @param mixed $value
+   *   The value of the property to set.
+   *
+   * @return static
+   */
+  public function prependProperty($name, $value) {
+    $property = &$this->getProperty($name);
+    $value = $value instanceof Element ? $value->getArray() : $value;
+
+    // If property isn't set, just set it.
+    if (!isset($property)) {
+      $property = $value;
+      return $this;
+    }
+
+    if (\is_array($property)) {
+      \array_unshift($property, Element::create($value)->getArray());
+    }
+    else {
+      $property = (string) $value . (string) $property;
+    }
+
+    return $this;
+  }
+
+  /**
+   * Gets properties of a structured array element (keys beginning with '#').
+   *
+   * @return array
+   *   An array of property keys for the element.
+   */
+  public function properties() {
+    return CoreElement::properties($this->array);
+  }
+
+  /**
+   * Renders the final element HTML.
+   *
+   * @return \Drupal\Component\Render\MarkupInterface
+   *   The rendered HTML.
+   */
+  public function render() {
+    /** @var \Drupal\Core\Render\Renderer $renderer */
+    $renderer = \Drupal::service('renderer');
+    return $renderer->render($this->array);
+  }
+
+  /**
+   * Renders the final element HTML.
+   *
+   * @return \Drupal\Component\Render\MarkupInterface
+   *   The rendered HTML.
+   */
+  public function renderPlain() {
+    /** @var \Drupal\Core\Render\Renderer $renderer */
+    $renderer = \Drupal::service('renderer');
+    return $renderer->renderPlain($this->array);
+  }
+
+  /**
+   * Renders the final element HTML.
+   *
+   * (Cannot be executed within another render context.)
+   *
+   * @return \Drupal\Component\Render\MarkupInterface
+   *   The rendered HTML.
+   */
+  public function renderRoot() {
+    /** @var \Drupal\Core\Render\Renderer $renderer */
+    $renderer = \Drupal::service('renderer');
+    return $renderer->renderRoot($this->array);
+  }
+
+  /**
+   * Sets the current form state for the element.
+   *
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   Optional. The current state of the form.
+   *
+   * @return static
+   */
+  public function setFormState(FormStateInterface $form_state) {
+    $this->formState = $form_state;
+    return $this;
+  }
+
+  /**
+   * Sets the value for a property.
+   *
+   * @param string $name
+   *   The name of the property to set.
+   * @param mixed $value
+   *   The value of the property to set.
+   * @param bool $recurse
+   *   Flag indicating wither to set the same property on child elements.
+   *
+   * @return static
+   *
+   * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
+   */
+  public function setProperty($name, $value, $recurse = FALSE) {
+    $this->array["#{$name}"] = $value instanceof Element ? $value->getArray() : $value;
+    if ($recurse) {
+      foreach ($this->children() as $child) {
+        $child->setProperty($name, $value, $recurse);
+      }
+    }
+    return $this;
+  }
+
+  /**
+   * Removes a property from the element.
+   *
+   * @param string $name
+   *   The name of the property to unset.
+   *
+   * @return static
+   */
+  public function unsetProperty($name) {
+    unset($this->array["#{$name}"]);
+    return $this;
+  }
+
+}
diff --git a/src/Utility/Variables.php b/src/Utility/Variables.php
new file mode 100644
index 0000000000000000000000000000000000000000..19d5053ce8045fa6084005acd90f1227afc5a363
--- /dev/null
+++ b/src/Utility/Variables.php
@@ -0,0 +1,116 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\ui_suite_bootstrap\Utility;
+
+/**
+ * Class to help modify template variables.
+ *
+ * @ingroup utility
+ */
+class Variables extends DrupalAttributes {
+
+  /**
+   * An element object.
+   *
+   * @var \Drupal\ui_suite_bootstrap\Utility\Element|false
+   */
+  public $element = FALSE;
+
+  /**
+   * Element constructor.
+   *
+   * @param array $variables
+   *   A theme hook variables array.
+   */
+  public function __construct(array &$variables) {
+    $this->array = &$variables;
+    if (isset($variables['element']) && Element::isRenderArray($variables['element'])) {
+      $this->element = Element::create($variables['element']);
+    }
+    elseif (isset($variables['elements']) && Element::isRenderArray($variables['elements'])) {
+      $this->element = Element::create($variables['elements']);
+    }
+  }
+
+  /**
+   * Creates a new  \Drupal\ui_suite_bootstrap\Utility\Variables instance.
+   *
+   * @param array $variables
+   *   A theme hook variables array.
+   *
+   * @return \Drupal\ui_suite_bootstrap\Utility\Variables
+   *   The newly created variables instance.
+   */
+  public static function create(array &$variables) {
+    return new self($variables);
+  }
+
+  /**
+   * Retrieves a context value from the variables array or its element, if any.
+   *
+   * @param string $name
+   *   The name of the context key to retrieve.
+   * @param mixed $default
+   *   Optional. The default value to use if the context $name isn't set.
+   *
+   * @return mixed|null
+   *   The context value or the $default value if not set.
+   */
+  public function &getContext($name, $default = NULL) {
+    $context = &$this->offsetGet($this->attributePrefix . 'context', []);
+    if (!isset($context[$name])) {
+      // If there is no context on the variables array but there is an element
+      // present, proxy the method to the element.
+      if ($this->element) {
+        return $this->element->getContext($name, $default);
+      }
+      $context[$name] = $default;
+    }
+    return $context[$name];
+  }
+
+  /**
+   * Maps an element's properties to the variables attributes array.
+   *
+   * @param array $map
+   *   An associative array whose keys are element property names and whose
+   *   values are the variable names to set in the variables array; e.g.,
+   *   array('#property_name' => 'variable_name'). If both names are identical
+   *   except for the leading '#', then an attribute name value is sufficient
+   *   and no property name needs to be specified.
+   * @param bool $overwrite
+   *   If the variable exists, it will be overwritten. This does not apply to
+   *   attribute arrays, they will always be merged recursively.
+   *
+   * @return $this
+   *
+   * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
+   */
+  public function map(array $map, $overwrite = TRUE) {
+    // Immediately return if there is no element in the variable array.
+    if (!$this->element) {
+      return $this;
+    }
+
+    // Iterate over each map item.
+    foreach ($map as $property => $variable) {
+      // If the key is numeric, the attribute name needs to be taken over.
+      if (\is_int($property)) {
+        $property = $variable;
+      }
+
+      // Merge attributes from the element.
+      if (\strpos($property, 'attributes') !== FALSE) {
+        $this->setAttributes($this->element->getAttributes($property)->getArrayCopy(), $variable);
+      }
+      // Set normal variable.
+      elseif ($overwrite || !$this->offsetExists($variable)) {
+        $this->offsetSet($variable, $this->element->getProperty($property));
+      }
+    }
+    return $this;
+  }
+
+}