Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough
Jan 09 2024
Jan 09

Part 1 | Part 2 | Part 3

In the second episode of our three-part series on the ETL Migration process, we delve into the most involved stage of Drupal migration: Transformation. This episode features insights from Tag1 Consulting’s experts, including Mike Ryan, co-creator of Drupal Migrate, and notable contributor Benji Fisher. They analyze the Transformation phase in the ETL process, specifically examining Drupal’s unique “row-by-row” approach, and the discussion session revolves around the advantages and challenges of this method, with a strong emphasis on optimizing performance within the transformation pipeline.

The episode is a treasure trove for those considering or currently working on Drupal migrations, as it steps into the technical realm while touching on the practical aspects of transforming data during a migration. Benji expresses his fascination with this particular stage, describing it as a playground for innovation and detailing the intricacies that make it the heart of the migration process. This discussion is essential for developers and IT professionals seeking to understand or undertake large-scale Drupal migrations.

[embedded content]

Part 1 | Part 2 | Part 3

Please let us know if you have specific migration-related topics you'd like to see us cover. Or, reach out and let us know if we can be an active part of ensuring your migration is a success!

For a transcript of this video, see Unraveling the ETL Process:Transform.

Important Links

Image by WaveGenerics from Pixabay

Jan 09 2024
Jan 09

Layout Builder is a Drupal module that makes working with content easy

The advantages of Drupal CMS always include its friendliness to users who cannot write code. In most cases, these users are content managers. This is only natural: if you want to be seen in the market, lower the entry barrier. However, competitors such as Tilda and WordPress lowered it earlier.

From the day it launched, Tilda positioned itself as a platform where you could create websites with tons of pre-built layouts, drag and drop elements around the screen and arrange them however you like, and the result would still be great — just like the work of a professional web designer.

In 2018, WordPress, for its part, added the Gutenberg plugin to its core, which allowed you to edit the website using the modular approach: you could arrange content blocks, the header, the footer, and widgets in any order.

Tilda and Gutenberg do not require writing code. So, when it came to Drupal, the community suggested the Layout Builder module. In this post, we will discuss its working principles, peculiarities, and installation process.

Layout Builder glossary

In this section, we will give definitions to the terms related to creating a Drupal website with the help of the Layout Builder. We will also need these terms for our next post, where we will explain how to build pages using the Layout Builder. 

1. A Drupal section is a page area with a structure composed using one of the layouts (see next point). In Drupal, you can add sections before or after other sections and change their order. 

2. A Drupal layout, also known as a Drupal template, is a model that dictates how the content will be arranged in the section. Layouts can be accessed and added to the page using the Add Section function. You can choose from Drupal’s four default layouts: one-, two-, three- or four-column layout, or create your own. The layout consists of blocks.  

3. A Drupal block is an area within the layout intended for different content entities: headers, descriptions, images, users, etc. In Drupal, you can drag and drop blocks within the layout and between sections.

Is it a layout or a template?

When Drupal developers talk to each other, they use different terms but still understand each other. However, these notions are not identical because within a Drupal theme there are twig files that store HTML code, and these very files are called templates. But when we talk about development using the Layout Builder, we say “layout”.

layout builder drupal

drupal blockIf you choose a two-column layout, two blocks appear in the section

4. A Drupal object, or Drupal entity, is a PHP object that reflects the data retrieved from a repository (database or file). This can be data on the page, post, case (the Node entity); user’s name, e-mail, password (the User entity); images, pdf documents (the File entity).

drupal entityThe content entity panel opens when you click on the Add Block

5. A Drupal content entity is anything a site user sees and interacts with, from pages to comment fields. The blocks within the sections are filled with these entities.
6. Drupal content types include any static content that can be grouped by an attribute, for instance, according to page types: posts, products, news, sellers. Each content type contains a specific set of fields.
7. A Drupal field is a place in the database reserved for specific information. When you add a field in Drupal, you need to specify the type of content you want to add (text, link, image, etc.).
8. A Drupal node is any representative of a content type. For example, a page of any content type is considered a node in Drupal.

What is the Layout Builder?

The Layout Builder is a module that combines drag-and-drop and WYSIWYG principles: interface elements can be freely arranged on the page and you can see the result of your actions immediately. You get a finished page structure, but in most cases without CSS, which you have to write separately. The module first appeared in Drupal 8.5 as an experiment and became stable by May 2019 when version 8.7 was released. Today, the Layout Builder is part of the Drupal core. 

The module allows you to use one of the four ready-made layouts or to add and customize your layouts for a more original result. A page can consist of several sections, each built with its own layout. Layouts consist of blocks, and each block consists of content entities, such as the author name, publication date, buttons, comment section, data fields, and so on. Paragraphs are not content entities, although entities can refer to paragraphs. The Layout Builder does not allow you to add paragraphs to sections right out of the box, but the Drupal community has already come up with modules that allow you to do this by wrapping paragraphs in blocks.

Is the developer really no longer necessary?

It is important to note that a content manager simply fills the ready-made structure with content. But it’s better to rely on professionals to create this structure. A back-end developer will add fields, arrange content types, views, and other components used to build a page, while a front-end developer will style the structure according to the design. Therefore, it is too early to compare the Layout Builder to Tilda.

How did the Layout Builder come about?

The admin panel of a Drupal site has a Structure tab. This tab will take you to the Block Layout section, which is a part of the Blocks module. This is where the developer defines the page areas where specific blocks will be displayed. A reusable structure is created in the Custom Block Library.

drupal blocks module

drupal block layout

drupal custom block library

Sometime later, the Paragraphs module appeared. It is not included in the Drupal core, doesn’t support the drag-and-drop principle, and requires full developer involvement.

drupal paragraphs module

paragraphs

The Views module is used to display dynamic content requested from the database. You can use this module to create a page, but only with a simple predefined structure.

It is safe to say that these modules are the basis of the classic approach to developing Drupal sites. However, the standard set of their functions is not always sufficient to make the page look as the web studio client requires, so developers had to write custom code.

The Layout Builder has incorporated the best practices of working with classic modules. It allows you to structure all necessary types of content and gather all display settings in one place, so there would be no heaps of custom code under the hood of a complex website waiting for tedious optimization and code review.

Layout Builder features and benefits

  • Full customization of your content design, structure, and layout.
  • Drag-and-drop and WYSIWYG principles. Not only the blocks but also the fields of content types can be dragged.
  • Flexibility in creating customized pages.
  • Saving and using created Drupal layouts on all pages of an individual content type.
  • Layout configuration for different cases.
  • Creation of layouts for different types of content on the site, as well as for content with special designs. For example, this feature is indispensable when the owners of online stores want to highlight one or more of their products among others.
  • Modules included in the Layout Builder are easy to launch and do not require a new type of entity for launching.
  • The Layout Builder interface and the markup it creates meet level AA of the WCAG and ATAG accessibility standards.

Does the future lie with the Layout Builder?

It’s not that Drupal’s Layout Builder has suddenly made the Paragraphs, Blocks, and Views obsolete. Of course, you still need them because the module extends but by no means replaces them. There are many opinions on the Layout Builder: some developers would have preferred to use it for everything on the website, while others say that it is more convenient for developers to use paragraphs. However, for content editors, the Layout Builder is a key to working quickly and independently.

An ADCI Solutions’ developer on working with the LB:

In one of our projects, we were asked to develop several blocks, each of a different type and structure: for example, an image on the left, text on the right and vice versa, or just text and just an image. All content managers had to do was choose from a list of pre-designed blocks, arrange them on a page, fill them with content, and it all looked great at once. We created a page for them that showed all the blocks, about 20 of them. That way, they could see how everything would look like. As far as I know, they filled a lot of pages in this way and no longer needed our constant participation. I think this approach is the thing of the future.

And if you ask me which websites the Layout Builder creates the best, you will not get a clear answer (unlike React.js, for example, which is ideal for high-load interactive applications). 

Conclusion: the use of the module depends on preferences and circumstances, but the overall regard for the Layout Builder is quite high.

Jan 08 2024
Jan 08

Part one in a series of posts introducing Symfony Messenger, its ecosystem, and unique Drupal integrations to Drupal developers.

The SM project brings Symfony Messenger and its ecosystem together with Drupal.

Symfony Messenger is a powerful alternative to Drupal’s @QueueWorker, enabling real-time or precise execution of scheduled tasks. It’s also a viable replacement for hook_cron enabling you to schedule dispatch and processing of messages according to crontab rules, again in real-time, rather than waiting for server-invoked or request-termination cron.

Messenger can be used as a user-friendly alternative to Batch API, as the user's browser is not blocked, while integrations such as Toasty can be programmed to notify when the last of a “batch” of messages completes, communicating to the user via user-interface toasts.

The Drupal integration includes additional niceties, such as intercepting legacy QueueWorkers for processing data through the Symfony Messenger bus, and end-user UI notifying a user when tasks relevant to them have been processed.

During this and the following series of posts, we’ll be exploring the benefits of real-time processing and user-friendly features that improve the overall experience and outputs.

WorkerThe workerToastyToasty displaying notifications.

Messenger

First up, we’ll cover the main features of Symfony Messenger and how it works.

As a developer working with Messenger, the most frequent task is to construct message and associated message handlers. A message holds data, while a message handler processes the associated data.

A message is inserted into the bus. The bus executes a series of middleware in order, each of which can view and modify the message.

If a transport is configured, the message may be captured and stored for processing later.

Typically the bus, middleware, and transports are configured in advance and rarely changed. Message and message handlers are introduced often without needing other configuration.

  • Message — an arbitary PHP object, it must be serialisable.
  • Message handler — a class that takes action based on the message it is given. Typically a message handler is designed to consume one type of message.
  • Middleware — code that takes action on all message types, and has access to the containing envelope and stamps.
  • Bus — a series of middleware in a particular order. There is a default bus, and a default set of middleware.
  • Envelope — an envelope contains a single message, and it may have many stamps. A message always has an envelope.
  • Stamp — a piece of metadata associated with an envelope. The most common use case is to track whether a middleware has already operated on the envelope. Useful when a transport re-runs a message through the bus after unserialisation. Another useful stamp is one to set the date and time for when a message should be processed.
  • Transport — a transport comprises a receiver and sender. In the case of the doctrine database transport, its sender will serialise the message and store it in the database. The receiver will listen for messages ready to be sent, and then unserialise them.
  • Worker — a command line application responsible for unserialising messages immediately, or at a scheduled time in the future. Messages are inserted into the bus for processing.

The stars of the show are buses. One bus is ready out of the box, which comprises a series of ordered middleware. A message is dispatched into a bus, where each middleware has the opportunity to view and modify the message (and its envelope). It's unlikely you’ll need to think about middleware, as the default set may already be the perfect combination.

When a message is dispatched to a bus, you can choose to wrap it in an envelope and apply stamps like the DelayStamp . A message will always be wrapped in an envelope if you don’t do it explicitly.

Buses have a series of default middleware. The main middleware to note are the transport and message handler middlewares. When a transport is configured for messenger, the transport middleware will capture the message, serialise it, and store it somewhere. For example, a database in the case of the doctrine transport. Any middleware after the transport middleware are not executed, for now.

When running the worker, you are opting to choose which bus and transport to run. The command will listen for messages as they are stored, and if the time is right, messages will be unserialised and inserted into the bus. The message will begin its journey yet again, iterating through all the middlewares from the beginning. When the transport middleware is hit, it will detect the message has already been in the transport to prevent recursion. This is done by checking the ReceivedStamp stamp added to the message envelope.

Transports: synchronous, asynchronous

Out of the box, when a message is dispatched into the bus in a CLI or web request, it will be processed synchronously. All middleware will operate on the message in a set order, including the message handler middleware.

The greatest advantage of using Messenger is the ability to asynchronously handle messages outside of the thread they were originally dispatched. That is: asynchronously. This can be useful for improving the web request response times and reducing the memory usage and limit of web requests (allowing for more FPM threads on a machine). Bulky business operations that would typically, or should be, constrained by the limits of the web thread have more breathing room. A CLI runner/container may be set up with a little more memory and processing capability with the explicit direction to listen for messages and handle them in real-time, either as soon as possible or as scheduled.

Upcoming posts in this series will dive into aspects of Symfony Messenger and SM:

The next post covers the implementation of a message and message handler, and a comparison with Drupal core’s @QueueWorker plugins.

Jan 08 2024
Jan 08

Today we are talking about Portals, Community Websites, and Drupal with guest Ron Northcutt. We’ll also cover Private Message as our module of the week.

For show notes visit:
www.talkingDrupal.com/432

Topics

  • Why are you passionate about community sites
  • Different types of portals you’ve worked on
  • Common features
  • Why is Drupal a great fit
  • Why would you choose Drupal over a Saas or PaaS
  • What is unique about each community
  • How important is UX
  • What common content models do you see
  • Most important tip

Resources

Guests

Ron Northcutt - community.appsmith.com rlnorthcutt

Hosts

Nic Laflin - nLighteneddevelopment.com nicxvan
John Picozzi - epam.com johnpicozzi
Martin Anderson-Clutz - mandclu

MOTW

Correspondent

Martin Anderson-Clutz - mandclu

  • Module name/project name:
  • Brief description:
    • Have you ever wanted to include a full-fledged, ajaxified system for private messages between users on your Drupal site? There’s a module for that
  • Brief history
    • How old: created in Apr 2017 by Jaypan, a fellow Canadian, but the most recent release is by Lucas Hedding, who hails from Nicaragua, and is a prolific contrib maintainer in his own right
    • Versions available: 8.x-2.0-beta18 and 3.0.0 versions available, the latter of which works with D9 and 10
  • Maintainership
    • Actively maintained, latest release in Oct 2023
    • Number of open issues: 130, 4 of which are bugs on the 3.0.x branch
    • Test coverage
  • Documentation: does have a handbook, though the pages seem to date back to 2017, so hopefully the installation and setup hasn’t changed too much since then
  • Usage stats:
    • Almost 2,000 sites
    • Maintainer(s):
  • Module features and usage
    • With the Private Message module installed, users on your site can have permissions-based access to send private messages to each other
    • Messages and threads are fieldable entities, and in general the module is made to be highly configurable, so you can tailor it to meet your site’s specific needs
    • That includes the frequency for asynchronous operations like loading new messages, which can be done without a full page refresh. There’s also a companion module to use Node.js for the asynchronous operations, to reduce load on both the browser and the server
    • That also allows for browser push notifications, or you can use the integration with the Message module to send notifications via email, SMS, and more, including aggregating the notifications into digests
    • Companies often have a dedicated messaging solution like Slack or Teams that they use internally, but this can be a good solution for an extranet or vendor portal, where the users may represent a variety of organizations
    • It’s also worth mentioning that both Private Message and Message are included in the Open Social distribution, so that could be a way to try out a preconfigured setup
Jan 08 2024
Jan 08

The Visual Layout Suite module is a great improvement of the core editorial experience leveraging Layout Builder and many contrib modules. Basicailly, it enables Layout Builder, depends on several contrib modules that improves Layout Builder and ads its custom magic.

One of the most important features is the use of utility classes allowing the configuration of the look and feel easily from the frontend with an instant preview. Additionally, it allows to restrict the utility classes a user can use, so the user is not overwhelmed by the extra options and also the look and feel of the page can be limited to be compliant with the style guide of the site. New utility classes can be easily added and components can be configured to only use a reduced set of utility classes to un clutter the UI.

VLSuite integrates with any Drupal component that integrates with Layout Builder and more: entity fields, paragraphs, Views, SDC, medias, etc. And there's no dependency on any theme, external service or certain entities. The only soft dependency is a Bootstrap 5 theme to enjoy all the VLSuite features out of the box. However, you can use any theme as long as it provides the utility classes you require. T

All the previous information means you could add VLSuite to an existing site with minimal effort if it is Boostrap based. If your theme is not a Bootstrap theme but provides utility clases (like Tailwind CSS) you will need more work to map the utility classes and the CSS classes, but still you can use it. If your theme doesn't use utility classes at all you will need more effort to implement them in your theme, but still the VLSuite won't alter your site, you don't have to dump your theme but extend it.

But those sentences are empty and hard to visualize just reading a text. That's why we have created a demo video about some of the capabilities of the VLSuite. The idea is make a copy of the current Drupal.org home page just using the VLSuite a Bootstrap 5 theme with no other customization (apart from what is stated in the module's page).

The result is pretty similar, specially taking into account that the theme was no edited at all, not even the colors. It is just Radix subtheme.

Judge by yourself:

On the left, the Drupal.org home (January, 2024). On the right, the copy by the VLSuite module.

As you can see, there are differences. The colors are similar but different, the spacing is not the same, the VLSuite copy is missing the footer (was not included in the copy) and not all components have the same alignment, etc. But remember this has been done without any customization. Imagine the possibilities when you add the standard Drupal theming techniques plus adding your own utility classes.

Video demo of the VLSuite capabilities

See the complete demo:

The video is long, almost 40 minutes, so here is an index:

  1. 00:00 Intro
  2. 00:47 VLSuite and Utility Classes
  3. 02:01 Environment
  4. 02:58 Copying D.org home page: hero component
  5. 04:27 Using Utility classes
  6. 09:33 Copying D.org home page: Drupal 7 EOL banner component
  7. 11:32 Copying D.org home page: Ukraine banner component
  8. 13:13 Copying D.org home page: Drupal for component
  9. 20:31 Copying D.org home page: Drupal Con Sponsorship banner component
  10. 25:17 Copying D.org home page: CTA collection component
  11. 29:32 Copying D.org home page: Drupal in the Real World banner component
  12. 31:40 Copying D.org home page: Carousel component
  13. 35:53 Copying D.org home page: Last components
  14. 36:43 Outro

Comments on the video

Improvements

I could have enabled some utility classes to mimic more closely the copied page. Also, with content types for the CTAs of the "By Industry" and "By Feature" sections their fields (even paragraphs) could have been used. For those sections a View is the best option. However, I wanted to keep the demo as simple as possible.  But remember those options are open with the VLSuite.

Performance

There are some concerns about using Layout Builder because of the performance. VLSuite heavily relies on Layout Builder and the landing page that mimics the Drupal.org home page has a lot of blocks and entities. However, profiling the page with Webprofiler the results are good:

   Memory   Time Empty home page  ~40Mb ~260 ms D.org copied home page   ~50Mb ~900 ms

The empty page is just the site's home page with no content displayed. Both pages were loaded after clearing the caches and loading some other pages so the general cache is warmed but not the contents of the pages being tested. While the VLSuite page required more resources, something expected given that the page had content while the other page was empty, there's no big difference. And the VLSuite features a lot of components.

Final words

We consider this video a good example of what can be accomplished using the VLSuite. The module is under heavy development and we plan to add more features and improve the current ones. If you think this module is useful please use it and give us any feedback on issues or improvements. 

Jan 07 2024
Jan 07

Drupal has a quick and convenient way of creating ajax dialogs that gives users the ability to embed links in content that open up dialog boxes when clicked. This is a useful way of presenting a bit of content to a user without them navigating away from the page.

I have previously written in detail about creating ajax dialogs in Drupal, and I refer back to that article quite often when the need arises.

The simplest way of creating an ajax dialog is by adding the class "use-ajax" and the data-dialog-type attribute, which can be one of dialog, dialog.off_canvas, dialog.off_canvas_top and modal. Using the "use-ajax" class tells Drupal that this is an ajax link and to intercept the click to perform an ajax request.

node in dialog

You can also inject options into the HTML to change some of the options in the dialog. For instance, if we wanted to set the width of the dialog to be 70% of the screen size then we would add the data-dialog-options attribute to the link.

node in dialog

Both of these links will open the page at "/node/1" in a dialog window instead of taking the user to the new page.

I recently had a requirement on a site that needed a dialog to re-open if the page was bookmarked or shared with another user. The page in question had a number of dialogs that presented short form content to the user without them needing to reload the page. By default, there is no state set when opening a dialog so I needed to add extra functionality to the dialog system to provide this feature.

I found that the best way to add this state was by appending the hash value like "#node/123" to the end of the URL. This meant that as the page loaded I could look for this hash value and load the ajax dialog for the user. Unlike query parameters, hash values are ignored by Drupal and so I wouldn't have to worry about filtering them out or causing unintended side effects.

The first step was to add a custom JavaScript library called "mymodule/node_modal" to every page load using the hook_page_attachments() hook.

/**
 * Implements hook_page_attachments().
 */
function mymodule_page_attachments(array &$attachments) {
  $attachments['#attached']['library'][] = 'mymodule/node_modal';
}

The node_modal library has a pretty simple structure. We just want to inject a bit of custom JavaScript code into the page and ensure that the JQuery and Drupal ajax libraries are present on the page as well.

node_modal:
  version: 1.0
  js:
    js/node_modal.js: {}
  dependencies:
    - core/jquery
    - core/drupal.ajax

The JavaScript library contains a bit of complexity, so I'll break this down bit by bit.

The first thing to do is to create a "Drupal.behaviors" area that will contain all of the custom JavaScript.

(function nodeModalControl($, Drupal) {
  'use strict';

  Drupal.behaviors.nodeModalControl = {
    attach(context, settings) {

      // All JavaScript goes here.

    }
  };
})(jQuery, Drupal);

When the page is first loaded we need to go through all the elements on the page that contain the data-dialog-type data attribute and add a click event. This click event will append the path of the link that was clicked to the end of the URL as a hash value.

// Find all modal links on the page and attach a click event to them.
const modalLinks = document.querySelectorAll('[data-dialog-type]');
for (let i = 0; i < modalLinks.length; i++) {
  modalLinks[i].addEventListener('click', function openDialogClick(event){
    // When the link is clicked, add the path to the URL as a hash value.
    history.pushState('', '', `#${this.pathname}`);
    event.preventDefault();
  });
}

With this in place, when a user is on the URL "/node/2" and clicks on a link that looks like this.

node in dialog

The URL will change to "/node/2#/node/1" before the dialog is opened. This gives a convenient way of storing what dialog box is currently open that can be easily sent to other users by just sharing the URL.

Of course, we don't want to keep the hash in the URL so we need a mechanism to remove this once the dialog has been closed. Thankfully, the dialog comes with a number of events that we can use to trigger our own code. When a dialog closes the event "dialog:afterclose" is triggered, which we can then listen to and reset the URL back to its original state.

// When the dialog is closed then remove the hash from the URL.
$(window).on('dialog:afterclose', (e, dialog,$element) => {
  history.pushState("", document.title, window.location.pathname + window.location.search);
});

The parameter "window.location.pathname" property contains the current path of the page and the "window.location.search" property contains any query parameters that might exist on the page. By doing this we preserve any functionality that might depend on query strings being present in the page.

Finally, we need a way of detecting the presence of our hash value on page load and triggering our dialog to appear.

This block of code provides this functionality. Here, we detect the presence of the hash in the URL, extract it, and then use the Drupal.ajax() function to trigger the dialog. The settings we pass to the Drupal.ajax() function here are essentially the same options we use for the original dialog link, just as an array of options.

// Run once the document has loaded.
once('init-once', context === document ? 'html' : context)
  .forEach(function initOnce(doc) {
    if (context.hasOwnProperty('location') === false) {
      // If the context has no location then this is a modal window then
      // we do nothing.
      return;
    }
    if (context.location.hash !== '') {
      // Extract the hash value from the URL.
      const hash = location.hash.substring(1);
      // Create the settings required for the ajax callback.
      var ajaxSettings = {
        'url': `${hash}`,
        'dialogType': 'dialog',
        'dialog': {
          'width': '70%'
        },
      };
      // Create the ajax callback object and execute it.
      var modalAjaxObject = Drupal.ajax(ajaxSettings);
      modalAjaxObject.execute();
    }
  });

Critically important to this is to detect if the current context being addressed to determine if it has a location property. This is because the dialog is loaded every time the page loads, including any ajax events that might be triggered. If the context does not have a location then we are looking at the HTML fragment being loaded inside the ajax dialog and can ignore it. Without this simple check in place the dialog would trigger recursively until the browser ran out of memory.

With this JavaScript in place the user will now be presented with an ajax dialog as they visit the page with a hash link to another item of content in it.

Note that this only works with node links. If you want to allow this for different types of content or custom modal links then you'll need to change the 'url' setting you pass to the Drupal.ajax() function and create some sort of controller that will react to the URLs being passed to it.

One limitation of this approach is that any path can be appended to the URL to force it to load onto the page. Drupal's permissions system is still used here so it's not actually possible to load protected content in this way, but you might see an error message that states "Oops, something went wrong. Check your browser's developer console for more details." if there was an error when loading the page. The Cross-Origin resource sharing (CORS) permissions system prevents arbitrary full URLs from being passed to the hash value as well, although the same error is produced.

In the project I created I solved these issues by using a custom controller to listen to the dialog callbacks, which simplifies the ajax settings a little since the dialog options are set in the controller.

// Run once the document has loaded.
once('init-once', context === document ? 'html' : context)
  .forEach(function initOnce(doc) {
    if (context.hasOwnProperty('location') === false) {
      // If the context has no location then this is a modal window then
      // we do nothing.
      return;
    }
    if (context.location.hash !== '') {
      // Extract the hash value from the URL.
      const hash = location.hash.substring(1);
      // Create the settings required for the ajax callback.
      var ajaxSettings = {
        'url': `/some/ajax/endpoint/${hash}`
      };
      // Create the ajax callback object and execute it.
      var modalAjaxObject = Drupal.ajax(ajaxSettings);
      modalAjaxObject.execute();
    }
  });

Using this mechanism I could control how the ajax request was responded to and could therefore control the validation of the input and what sort of data was returned from the request. This did mean that the ajax dialog links need to point to a different URL, and they don't use the "data-dialog-type" attribute any more since the link would always return an ajax dialog. Instead the links just have a class that can be easily added to generate the dialog link. To avoid any confusion, I abstracted the creation of the link away from the users so that they didn't need to worry about the implementation detail of creating the links.

Jan 07 2024
Jan 07

Introduction

Understanding your users is crucial for organizational success. A robust feedback system gathers data ( through reviews, surveys, social media, etc.) to unlock valuable insights. This identifies key areas for product improvement and informs data-driven decisions that optimize future offerings. 

The redesign of Airbnb, motivated by customer feedback, is an example of how well this principle works.

On May 3, 2023, Brian Chesky, the CEO of Airbnb, announced a significant platform redesign featuring over 50 improvements to enhance its customer experience. This initiative was driven by extensive user feedback gathered from diverse sources, such as:

  • Guest reviews
  • Host input
  • Surveys
  • Social media
  • Direct channels

Acting upon it enabled Airbnb to 

  • Understand user needs and trends
  • Improve functionality, usability, and platform performance
  • Prevent negative experiences and reduce operational costs
  • Increase host satisfaction and reduce guest complaints
  • Build trust and strengthen the brand's reputation

Airbnb's success hinges on its strong feedback system. To replicate these results, organizations need to solve challenges in setting up a strong feedback system. 

Quality Engineering (QE) offers valuable assistance in overcoming these hurdles.

Challenges Setting Up A Strong Feedback System

Data Processing

Data processing in feedback systems is about turning raw data into valuable insights by cleaning, classifying, and analyzing it. It's sorting and refining messy input to reveal hidden patterns and trends, a key ingredient for continuous improvement.

Lack Of Qualitative Data (Non-Numerical Information & Concepts)

Usability testing and user research can help build context by sharing insights on user motivations, behaviors, and pain points. 

Poor Data Quality

The QE team automates data quality tests integrated into the CI/CD pipeline. During product implementation, the team identifies anomalies in historical data through close collaboration and implements relevant scenarios into the test suite, ensuring comprehensive testing.

According to Gartner, each year, poor data quality causes organizations to lose an average of $12.9 million. Besides the direct impact on revenue, it complicates data systems and results in bad decisions over time.

Usability

Simple interfaces, clear instructions, and quick processes encourage more people to share their thoughts, providing valuable insights for improvements and better outcomes. The usability of a feedback system has to be top-notch.

Creating a Good Experience

Data about user actions and emotions can be used to create an empathy map. These maps guide design, ensuring each iteration resonates with real users, ultimately leading to delightful experiences. 

Feedback System Design

A good A/B test helps determine the preferred interface. Testers can assess user preferences by comparing variations and making data-driven decisions on visually appealing and impactful changes.

Netflix maximizes user engagement and revenue growth in the competitive streaming industry through a data-driven strategy. At the core of this approach is the extensive use of A/B testing. 

Netflix optimizes content recommendations and personalizes the user experience by continuously iterating and testing features and algorithms. 

This emphasis on A/B testing allows Netflix to refine its offerings based on real-time insights into customer preferences.

Scalability

Scalability in a customer feedback system refers to its ability to efficiently handle growing volumes of feedback data, users, and interactions. It involves seamlessly adapting and expanding the system to accommodate increased demand without compromising performance or the user experience.

Feedback Volume Surge

Conducting load testing by mimicking high user loads reveals performance bottlenecks within a system. This process aids in identifying areas for improvement, allowing optimization of server capacity to enhance overall performance and the user experience.

Security Concerns

A growing user base attracts more security risks. To protect the system, perform security audits, conduct penetration testing, and enforce strict access controls, ensuring robust defenses against potential vulnerabilities.

User Interface Responsiveness

Data surge poses the risk of slowing down the user interface. Frontend performance testing enhances code efficiency, reduces rendering delays, and ensures a responsive user experience.

Meta's AI training experienced bottlenecks due to the huge amount of data that slowed down their CPUs' data processing, which lagged behind the lightning-fast GPUs used for AI-model training. 

This and limited power budgets restricted the number of models they could run.

Meta implemented a comprehensive strategy to address these challenges, including building a new data ingestion infrastructure and last-mile transformation pipelines. A key component is the Disaggregated Data PreProcessing Tier (DPP), responsible for fetching, decoding, and transforming data for AI training. The DPP allows scalable data ingestion and independent scaling.

The optimizations led to a 35–45% improvement in the power budget required for data ingestion, enabling support for a growing number of AI models within power constraints.

Organizational Challenges

Organizational challenges in setting up a feedback system include securing stakeholder buy-in, defining clear objectives aligned with business goals, addressing potential resistance to change, and establishing a culture that values data-driven decision-making.

Stakeholder Buy-In

Generating a comprehensive report showcasing tangible benefits can positively influence stakeholder buy-in. Utilizing QE metrics to define SMART (Specific, Measurable, Achievable, Relevant, and Time-bound) goals for the feedback system is also convincing.

Fostering A Data-Driven Culture

QE values data as a learning tool for guiding informed decisions and ensuring inclusive products. With clear goals, shared celebrations, and stakeholder involvement, QE sets the stage for a data-driven culture. 

An example of a tangible benefit:

When it comes to the Cost Per Ticket (CPT), IT companies rank among the most expensive. The CPT value typically ranges from $25 to $35, but it can reach up to $100 depending on operational factors.

In a scenario with 500 monthly support tickets at $35 each, a 20% reduction through efficient QE results in 400 tickets.

Monthly cost savings amount to $3,500 ($17,500 - $14,000).

Data Privacy, Security, & Compliance

Establishing a feedback system involves navigating data privacy, security, and compliance challenges. Safeguarding user information, implementing robust security measures, and ensuring compliance with relevant regulations are critical aspects of creating a trustworthy and reliable feedback platform.

Identifying Vulnerabilities 

Security testing identifies vulnerabilities and establishes robust encryption protocols for secure customer data handling. Regular audits validate compliance with data protection regulations, ensuring the integrity of the feedback system.

Protecting Users' Privacy

Collecting only essential information, implementing secure storage with encryption, employing access controls, being transparent with users, complying with privacy regulations, and conducting regular audits contribute to privacy.

A 2023 survey by Deloitte claims that 50% of customers think that the benefits they get from online services are not worth worrying about data privacy. This highlights the importance of organizations showcasing their commitment to robust data privacy measures.

Businesses must clearly communicate their data practices, secure handling, regular audit details, and other security measures through various mediums. 

How Canva Made Their Customers and Team Happy with QE Approach 

Canva strategically enhanced its quality model despite resource constraints and tight deadlines. 

Their new Quality Assistance model embraces a proactive "shift-left" approach that involves addressing potential challenges at the early stages of development to prevent them from escalating. 

They leveraged data-driven decisions to optimize their quality process, which included

  • Prioritizing testing for the most-used features, which minimized critical bug encounters and boosted overall satisfaction.
  • Focusing on code coverage improved test efficiency, reduced reworks, and increased release confidence.
  • Balancing technical debt with new features, preventing future issues, and intuitive user experiences.
  • Understanding user behavior helped test high-traffic areas, reducing user friction points.

As a result, their team achieved better test coverage, reduced code rework, and increased confidence in feature releases, resulting in zero incidents in the past three months.

If you're eager to delve deeper into how QE can elevate your systems and processes, why not schedule a call with one of our QE experts?

Jan 06 2024
Jan 06

I was just recently starting to get this on projects after (at least I think that's why) I updated to Chrome 120.

      Could not open connection: unknown error: cannot find Chrome binary
        (Driver info: chromedriver=120.0.6099.71 (9729082fe6174c0a371fc66501f5efc5d69d3d2b-refs/branch-heads/6099_56@{#13}),platform=Linux 6.2.0-37-generic x86_64) (Behat\Mink\Exception\DriverException)

That's the error message in behat at least, which I think originates from the actual webdriver (chromedriver) response. If I look at the debug information from the chromedriver logs it says this:

[1703837483,910][INFO]: [0ca18bb59db30d5acd358de02a01da0a] RESPONSE InitSession ERROR unknown error: cannot find Chrome binary

Well the error is clear enough. It can not find the binary. That's fine by me, but where would I go about informing about the binary? Well, for me that would be in behat.yml:

@@ -33,8 +33,9 @@ default:
               w3c: false
           marionette: null
           chrome:
+            binary: /usr/bin/google-chrome
             switches:

Thanks to kostiukevych-ls in the comments, this seems like it would be something like this if you are on Mac OS:

@@ -33,8 +33,9 @@ default:
               w3c: false
           marionette: null
           chrome:
+            binary: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

This probably translates to something like this, while initiating the session with chromedriver (slightly edited for relevance and brevity):

[1703887172,675][INFO]: [95b2908582293fa560a7301661f5e741] COMMAND InitSession {
   "desiredCapabilities": {
      "chrome.binary": "/usr/bin/google-chrome",
      "chrome.extensions": [  ],
      "chrome.switches": [ "--ignore-certificate-errors", "--disable-gpu", "--no-sandbox", "--disable-dev-shm-usage" ],
      "goog:chromeOptions": {
         "args": [ "--ignore-certificate-errors", "--disable-gpu", "--no-sandbox", "--disable-dev-shm-usage" ],
         "binary": "/usr/bin/google-chrome",
         "extensions": [  ]
      },
      "ignoreZoomSetting": false,
      "marionette": true,
   }
}

If you are using some other tool that interacts with chromedriver, I am sure you are already setting some parameters there, which you could append this new parameter to.

Alternative solution, using browser name

For the attentive reader, you might contemplate over how practical it would be to have this change laying around locally. Good point. Chromedriver is actually supposed to find the chrome binary of itself, so why is it not in this case?

If you have some verbose logging enabled, you might notice this message also popping up:

[1708076533,081][DEBUG]: Unknown browser name: firefox

For me that was a bit surprising, since I did not ask for the firefox browser in any way I knew myself. However, since I am using behat using mink-selenium2-driver, firefox is actually set as the default browser name. If I am using chromedriver, it will try to use this value when it tries to find the binary.

To override it, all I needed to do was this (this is using the Drupal behat extension for the mink parameters):

diff --git a/behat.yml.dist b/behat.yml.dist
index 7bf8214b..958a9a25 100644
--- a/behat.yml.dist
+++ b/behat.yml.dist
@@ -21,6 +21,7 @@ default:
     Drupal\MinkExtension:
       files_path: '%paths.base%/tests/files'
       ajax_timeout: 15
+      browser_name: chrome

And voila. It now works both in CI environments and locally as well.

Jan 05 2024
Jan 05
Amanda LukerAmanda Luker

Amanda Luker

Web Chef Emeritus

Amanda is responsible for translating visual designs and applying them to Drupal sites.

January 5, 2024

Drupal provides a system for site administrators to add their own images and have them appear uniformly on the website. This system is called Image Styles. This tool can resize, crop, and scale images to fit any aspect ratio required by a design.

When creating responsive websites, a single image style for each image variation is insufficient. Each image, such as a hero image, a card image, a WYSIWYG image, or a banner image, requires multiple versions of one image. This ensures that the website delivers only what visitors need based on their screen size. For instance, a mobile user may only require a 320-pixel-wide image, while a large desktop user may want an 1,800-pixel-wide image (doubled for double-pixel density). For this reason, Drupal has Responsive Image Styles, which will group your images into a set of styles that will each show under different conditions.

Practical approach to convert images from design to Drupal

  • Determine your image’s aspect ratio. If you find that the images in the design are not in a common aspect ratio (like 1:1, 2:1, 4:3, or 16:9) or if they vary by a little bit, consider running the dimensions through a tool that will find the closest reasonable aspect ratio.
  • Determine the smallest and largest image sizes. For example, for a 16:9 aspect ratio, the smallest size might be 320 pixels x 180 pixels, while the largest could be 3,200 pixels x 1,800 pixels (doubled for high-density screens).
  • To generate all variations, you can use an AI tool to print images with 160-pixel increments between each size. 160 increments tend to hit a lot of common breakpoints. Here’s an example using GitHub CoPilot:

There are likely more ways to streamline this process with Copilot. I’ve also used ChatGPT to rewrite them using a prefix, making it easy to add them in Drupal like this:

Drupal image styles

If adding all of these steps seems like a lot of work, consider using the Easy Responsive Images module! This module can create image styles for you, allowing you to set your aspect ratios and the increment between each style.

Once you have all your styles in place, create your responsive image styles by following these steps:

  • Choose a name for your responsive image style based on its usage
  • Select the “responsive image” breakpoint group
  • Usually, I choose to select multiple image styles and use the sizes attribute. Use the sizes attribute to craft your “sizes.” For example:

(min-width:960px) 50vw, (min-width:1200px) 30vw, 100vw

In this example, choosing an image that is smaller than 960 pixels will best fit the full width of the viewport. At 960 pixels, the image will be selected to best fill half of the viewport width, and at 1,200 pixels, 30%. This approach is nimble and allows the browser to choose the most appropriate image for each case.

After setting the size rules, choose all of the image styles that you want the browser to be able to use. You don’t have to use them all. In some cases, you might have two responsive image styles that are pulling from the same aspect ratio image styles, but one uses all of them and the other uses a subset of them.

Drupal image sizingDrupal image sizing

After adding your responsive image style, you need to map your Media View Mode:

  1. Go to https://[your-site.local]/admin/structure/display-modes/view/add/media
  2. Add the media view mode as a new Display for Images: https://[your-site.local]/admin/structure/media/manage/image/display
  3. Choose “Responsive image” as the Format and select your new responsive image style

Drupal responsive image manage displayDrupal responsive image manage display

Once you have set this up, you are ready to use the View Mode to display the image field for your entity.

Drupal article with imageDrupal article with image

In this example, all the images have the same breakpoint. There may be times when you need to have different aspect ratios at different breakpoints. In those cases, you may want to use your custom theme’s Breakpoint Group. This will allow you to manually select each image style on for each breakpoint (instead of letting Drupal choose it for you).

Making the web a better place to teach, learn, and advocate starts here...

When you subscribe to our newsletter!

Jan 05 2024
Jan 05

Don't Miss the Last Chance to Submit Your Idea

With over 40 sessions submitted to date, we have a couple of days left for you to get your ideas in for MidCamp 2024!

We’re open to talks for all levels of Drupal users, from beginner through advanced, as well as end users and business owners.

See our session tracks page for a full description of the kinds of talks we are looking for.  If you’re new to speaking, check out the recording of our Speaker Workshop for ideas.

Submit a session now!

Important Dates:

  • Proposal Deadline: January 7 (Sunday), 2024 at midnight CST
  • Tickets on sale: very soon!
  • Early-bird deadline and speakers announced:  February (all speakers are eligible for a free ticket, and anyone who submits a session that is not accepted will be eligible for early-bird pricing even after it closes)
  • MidCamp 2024: March 20-22

Sponsors Get Early, Privileged Access

Get early and privileged access to new talent and customers by sponsoring MidCamp. We have a variety of sponsorship packages available.

Starting at $600, sponsoring organizations can target their jobs to a select group of experienced Drupal talent, maximize exposure by sharing space with dozens of jobs instead of millions, and have three days of being face-to-face with applicants.

Our sponsorship packages are designed to showcase your organization as a supporter of the Drupal community and provide opportunities to:

  • grow your brand,
  • generate leads,
  • and recruit Drupal talent.

Check out the sponsorship packages here, we look forward to working with you to get your organization involved for 2024!

Stay In The Loop

Join the MidCamp Slack and come hang out with the community online. We will be making announcements there from time to time. We’re also on Twitter and Mastodon.

We can’t wait to see you soon! Don’t forget, cancel all those other plans and make MidCamp the only thing happening on your calendar from March 20-22, 2024.

Jan 05 2024
Jan 05

Sometimes, creating a modern design with high functionality is problematic. Especially when it comes to large-scale and complex projects. There are various solutions to make the process of creating layouts easier and more convenient. One of the most influential and popular is Drupal Layout Builder.

What is the Layout Builder in Drupal?

Convenience, usability, design solutions, and the ability to provide users with information, goods, and services in an accessible form directly affect traffic growth and the site's popularity. This is important for any Internet resources, including extensive online stores, news sites, landing pages, etc.
Drupal Layout Builder helps you design web pages without programming knowledge (almost). The predecessor of the Layout Builder is the Display Suite Node that runs in Drupal. However, the Layout Builder is more advanced and gives more options. The main difference is the ability to define each "node" layout separately. This way, you can create more varied and attractive page designs. This is especially important when creating a site with many pages. So, you can create pages according to a given template and then change the design of each page if you wish.
Drupal Layout Builder is a tool that allows you to create three types of content - ads, articles, and pages (structure/content types/). You can select blocks of information and multimedia for each content type and set their location. The first time after the launch of the experimental module, it was unstable, but now the errors have been fixed, and it can be used without problems.

Getting started with Drupal Layout Builder

To get started with this module, you must install it. The Layout Discovery module will also be installed with the Layout Builder module.

  • First, we add fields in the "Manage fields" tab. These fields will appear on a future page. Each field corresponds to a type of content, such as image, video, text fields with information, etc. You can select options for each field by clicking the edit button on the right.
  • Next, we need to choose the type of content that will be placed on the site (Structure\Content types). Also, other options are available for each of the content types. You can manage: form display, manage display, manage fields, or delete.
  • Next, you need to set up the blocks. The display of content on the page will depend on what settings you choose. This allows you to optimize the creation of site page design, speed up the process and make it more efficient. The block design is customizable here.
  • Also, when using Drupal 8 and above, you can set the administration language. To do this, set the desired languages in the admin/config/regional/language page.

For example, let's create a home page. To do this, select the Basic page option, then select "Layout." Each layout consists of one or more columns (blocks), and you can set the width of the blocks on the page. Next, add information and pictures to the appropriate blocks. Do not forget about the feedback field, it is usually moved down. As you can see, nothing complicated. Do not forget to save the layout that you created.

Creating a basic page with the Layout Builder

You can create personalized layouts without much effort. If you want to create a portfolio page, you can do it like this:

  • Select the “Basic” page option. Next, click “Add section” and choose the appropriate layout. Next step: add blocks by clicking on the Add block field. Don't forget to select blocks for your projects and customer feedback. Information can be transferred to blocks using the drag-and-drop method.
  • It is recommended to turn on the “Allow each content item to have its layout customized” option (tab Manage display). This will bring up the Layout tab and allow you to change the layout of a particular page. It will help if your portfolio includes several pages.

How to add dynamic content with the Layout Builder?

The Layout Builder makes adding blocks for dynamic content easy and convenient. To integrate dynamic content into a page, you just need to select the appropriate block from the menu but don’t forget about “Manage fields.”
Also, this can be done as part of creating custom blocks. Click on the Add block field and select "Create a custom block" at the top of the menu. Next, you need to form a block using the available options. Each such block is associated with the current layout.
Let's try to create a blog feed in a multi-column layout. To do this, click “Add section” and select Layout. Choose a layout with two columns. Now you can add text to one of the columns and add an image or video to the other. The first blog post is ready. To make the next blog post, click the Add section box under your first post and select a new layout. It does not have to be the same as the previous one - be creative with the process.

How to create complex pages with the Layout Builder?

Complex pages are not complicated at all. Such a page will include text, an image, and perhaps even a video by default. Or there can be many ads on a page, but the Layout Builder allows you to optimize the process of creating pages through layouts. At the same time, do not forget that you can edit each page separately, as we have already discussed.
To create a page with a product, you need to follow the steps we have already described earlier. Click “Add section” and select Layout (we discussed how to assign fields for different content types earlier). You can use an image, slideshow, or video to present a product. Having selected the content type in the “Manage fields” tab and selecting the necessary options, we transfer it to the appropriate blocks. If necessary, you can create a custom block. 
To create one view inside another, the easiest way is to install the “Field view” module immediately. This module is recommended to be installed along with the Layout Builder to explore your possibilities fully. Once the module is installed, you can place one view inside another. To do this, in the “Fields” section, you need to click the “Add” button and select “View.” This will turn your field into a View. Next, the “Settings” window will appear; you do not need to choose anything, click “Apply.” Next, in the “Fields” section, click “Global View” and select a view from the list. Now a second one will appear inside the first view.
In the “Fields” section, you can also change or delete the Title. Below the page, there is a preview window, which is very convenient. The next step is to select the number of items to display. Remember that too many complex blocks will slow the page load time.

Why else use the Layout Builder?

Adaptive design

In today's world, the adaptability of website design is crucial. The ability to conveniently view pages in the mobile version always wins the audience's approval. 
The Layout Builder is responsive by default. For example, when you choose a layout, you can set the width of each of the boxes as a percentage. This is convenient, but when your layout includes three horizontal boxes, it can look ugly on a mobile device. 
The most common way to adapt website design to mobile devices is with the Bootstrap Layout Builder module. The module can be used with the Layout Builder, which means you can adapt layouts to different types of devices. After installing and enabling the module, the "Breakpoints" tab will display desktop, tablet & mobile options. 

Importance of SEO

The modern user appreciates the convenient and stylish design of web pages. Such a design should provide all the necessary information, including video images and other blocks allowing the user to know the product or service better. Using modules like the Layout Builder enables you to create a modern, user-friendly design that automatically attracts new users to your site. As a result, the user is satisfied and spends more time on the site's rating grows.

Load time and caching

Load time is an important issue, as a long page load can cause a user to leave your site before it loads. The most common mistake when creating pages is overloading them with complex views and much media content.
Creating a view inside a view or using a custom block is very handy, but it increases the page load time. Therefore, the most straightforward and practical advice is to use complex blocks only if necessary.

Leverage Drupal Layout Builder to create an attractive site!

The Drupal Layout Builder module makes creating attractive web pages much faster and easier. Even creating complex pages takes much less time due to convenient features and layouts. 
For example, you can set a template for an online store according to which all subsequent pages will be created. This way, you don't have to create each page separately. At the same time, you can create an individual design if it is necessary. 
Make experiments and apply knowledge in practice to gain practical skills. Or ask our Drupal developers to create complex pages for your project.

Jan 05 2024
Jan 05
, etc., these elements create natural landmarks that keyboard users can navigate to. 

Menus need to have a ‘nav’ element, which provides a clear indication of the section’s purpose. HTML heading tags (from

) establish a clear hierarchy of content, from main headings to subsections. 
  • Skipping to main content

By including a “Skip to Main Content” link at the beginning of the page, you enable keyboard-only users to bypass navigation menus, headers, and other content that may be repeated across multiple pages and jump directly to the main content.

  • Descriptive links

When a user navigates a web page using the “Tab” key and encounters a link, they need to know the purpose or destination of the link. So it’s necessary to provide clear, meaningful, and concise link descriptions. An example of a good link description would be “Explore our range of services” and an example of a bad one — “Click here.”

  • Interaction with the focused elements

Users often need to make specific interactions with the website’s elements that come into focus. This involves additional keys other than the “Tab” key. For example, arrow keys help users navigate the list of options or between items in a menu, a carousel, or a slider. Another example is that the “Enter” or the “Spacebar” key activates the element, like opening a link or clicking a button. The specific keys depend on the browser and the operating system, as well as the implementation on a website or in a CMS.

Using the up and down arrow keys to choose the items in a dropdown menu of Drupal’s default admin theme Claro.Using the up and down arrow keys to choose the items in a dropdown menu of Drupal’s default admin theme Claro.

A closer look at keyboard accessibility in Drupal

The Drupal CMS supports accessible keyboard navigation out of the box. The best practices of semantic HTML5 are extensively used in it, the “Skip to Main Content” link is included at the beginning of pages, consistent focus styling is present across all elements in its core themes, the proper tabbing order is in most cases defined out of the box, and so on. All default elements already work with the keyboard accessibility support, so developers basically need to only take care of it when creating custom ones. 

Focus style for the login form in the Drupal’s default frontend theme Olivero.Focus style for the login form in the Drupal’s default frontend theme Olivero.Focus style for the “Add content” button in the administrative panel provided by Drupal’s admin theme Claro.Focus style for the “Add content” button in the administrative panel provided by Drupal’s admin theme Claro.The “Skip to Main Content” link that appears during keyboard navigation at the top of a Drupal administration page.The “Skip to Main Content” link that appears during keyboard navigation at the top of a Drupal administration page.

Tabbing Manager 

One of the prominent features that helps developers make sure they create keyboard accessible web pages is Drupal.tabbingManager. It is a JavaScript class that enables developers to create a logical navigation flow, helping users quickly access the needed elements. The Tabbing Manager was introduced in Drupal 8 as part of an outstanding “wave” of massive accessibility enhancements. 

Tabbing Manager is particularly useful when you need to guide the user through completing a particular task, like filling out a form or interacting with a specific feature. In this case, you want to limit the focusable elements that can be navigated using the “Tab” key. Instead of having to tab throughout the entire page, the user can only use those that are relevant to the current context. This approach is referred to as constrained tabbing, sometimes referred to as capturing and trapping keyboard focus. At the same time, there must be a clear and accessible way to move focus out of the area with the help of standard keyboard methods, making sure the user isn’t literally trapped there.

Tabbing Manager relies on two methods to implement constrained tabbing:

  • The constrain method. When a set of elements is passed to this method, pressing the “Tab'' key will provide navigation within this set of elements only.
  • The release method. This is the method to remove the tabbing constraint once the user has completed the specific task. 

It needs to be noted that when a tabbing constraint is active, you need to use the Drupal.announce() feature to aurally inform the screen reader user of the constraint and what key to use to exit it.

Here is an example implementation from drupal.org for using the constrain and the release methods, as well as creating live announcements.

// Constrain to the "edit mode toggle" and contextual links.
var tabbingContext = Drupal.tabbingManager.constrain($('.contextual-toolbar-tab, .contextual'));

// Announce the tabbing constraint.
var args =  {
  '@contextualsCount': Drupal.formatPlural(Drupal.contextual.collection.length, '@count contextual link', '@count contextual links')
};
Drupal.announce(Drupal.t('Tabbing is constrained to a set of @contextualsCount and the edit mode toggle.', args));
Drupal.announce(Drupal.t('Press the esc key to exit.'));
// The task was completed; release the tabbing context.
tabbingContext.release();

// Depending on the use case, announce this as well.

A step-by-step example of a keyboard-only workflow in Drupal’s CKEditor

One of the most vivid examples of keyboard accessibility in the Drupal core is the workflow for embedding media from the Media Library to Drupal’s CKEditor. Let us walk you through this process without ever touching the mouse. 

This example is in CKEditor 5 but the functionality is fully operable in CKEditor 4 as well. Please note that for this to work, you’ll need to make sure the Media Library button is dragged to the active toolbar in “Text formats and editors” for the format you’re using (e.g. Full HTML) and the “Embed media” filter is enabled down the same page.

NB. Please note that the use of the keys might depend on the settings of the web browser and the operating system. In our example, the Chrome browser and the Windows OS are used. You might want to make sure your browser is configured to use the key to move between links and fields:

  • The “Press Tab to highlight each item on the webpage” checkbox must be checked on the Advanced tab of the Safari browser settings.
  • The “Use the tab key to move focus between form control and links” checkbox must be checked on the General tab of the Firefox browser settings.

Coming back to our example, when you are on the Content page of your Drupal admin dashboard, you can use your keyboard to do the following:

  • Tab to the “Add content” link and hit “Enter” to open it.
  • Tab to the “Article” link on the “Add content” menu and hit “Enter” to open it.
  • (Optional) If you need to change the text format (e.g. Full HTML), tab to "About text formats" and click “Tab” + “Arrow,” then use “Arrow” to select the needed text editor from the dropdown. 
  • Click “Shift” + “Tab” twice, which will take you to the “Text formats” help link and then to the “Body” area in CKEditor.
  • Click “Alt” + “F10” to jump to the CKEditor toolbar for Windows (this might be the “Fn”+ “Option” + “F10” on Mac OS).
  • Use “Arrow” keys to go through the toolbar icons and reach the Media Library icon.
Finding the Media Library icon with keyboard on CKEditor toolbar.Finding the Media Library icon with keyboard on CKEditor toolbar.
  • Click “Enter” to click the icon, which will open the Media Library.
  • Tab through the Media Library to choose the specific media item (in this example, an image), then click “Space” to select it.
Selecting an item in Drupal’s Media Library with keyboard.Selecting an item in Drupal’s Media Library with keyboard.
  • Click “Tab” + “Enter” to give focus to the “Insert selected” button, then click “Space” to embed the image in CKEditor.
  • Use “Alt” + “F10” to give focus to the media toolbar. Use “Arrow” keys to move between the media toolbar options for adding a caption, adding a link, overriding the Alt text, and adjusting the image. Click “Enter” to activate the specific option. 
Giving focus to the multimedia toolbar with keyboard in Drupal’s CKEditor.Giving focus to the multimedia toolbar with keyboard in Drupal’s CKEditor.

[embedded content]

Examples of keyboard accessibility in contributed Drupal modules

The Drupal core as well as contributed projects strive to adhere to keyboard accessibility standards. A great example would be the Better Mega Menu module, which is one of the most popular contributed Drupal modules for menu creation. It provides an admin UI for creating dropdown menus that enrich Drupal menu items with other elements such as internal and external links, images, videos, and more.

Menus created by the Better Mega Menu module.Menus created by the Better Mega Menu module.

The Better Mega Menu module has a separate documentation page dedicated to its keyboard accessibility features for efficient navigation across the top-level menus and submenus using the Tab, the Left/Right Arrow, the Return, the Esc, the Up/Down, the Home, and the End keys.

Some contributed modules that improve keyboard accessibility

  • Devel Accessibility

The Devel Accessibility module simplifies the using and testing of keyboard accessibility functionalities. Among other things, it can log the Drupal.announce announcements to the browser console and visually indicate the reachable elements when tabbing constraints are applied by Drupal.TabbingManager.

Tabbing constraint visualization provided by the Devel Accessibility module.Tabbing constraint visualization provided by the Devel Accessibility module.
  • Keyboard shortcuts

With the help of the Keyboard shortcuts module, you can create shortcuts for various actions on a Drupal website. The shortcuts can be configured for single keys, combination of keys, sequences of keys. For example, it can be configured that “CTRL + N” anywhere on the website opens the “Add content” tab. There is also the support for tokens such as {user} and {node}.

The keyboard shortcut settings page provided by the Keyboard shortcuts module.The keyboard shortcut settings page provided by the Keyboard shortcuts module.
  • Tab Manager

The Tab Manager module enables you to hide, reorder and rename tabs for accessing specific tasks in the Drupal admin dashboard. This helps tailor keyboard navigation experiences to specific needs, making them more efficient.

Configuring tabs with the help of the Tab Manager module.Configuring tabs with the help of the Tab Manager module.
  • General accessibility modules

When it comes to improving or testing keyboard accessibility with the help of contributed modules, you might find it useful to check out general accessibility modules such as Accessibility toolkit, Editoria11y, CKEditor Accessibility Auditor, and others.

Final thoughts

It’s time to make sure your Drupal website provides accessible keyboard navigation experiences to all users. Unlock your website’s capabilities by allowing every action that is available on a mouse hover to also have the appropriate key on a keyboard. Luckily, the Drupal CMS is equipped with the right tools and practices for implementing this flawlessly.

Jan 04 2024
Jan 04
Published on Thursday 4, January 2024

I use GitHub to host my repositories, such as this website. To keep my dependencies up-to-date, I leverage Dependabot. The product has matured a lot over the past few years. Before, it was a standalone service and then acquired by GitHub. It did not support dependencies managed by Composer. It was pretty spammy and very noisy. However, it has drastically improved over the past few years. Thanks to all of those at GitHub who have worked to improve it (that includes you, Mike Crittenden.)

My Dependabot configuration consists of a few items, nothing overly specific.

  • Defining each ecosystem in my repository (GitHub Actions, Composer, NPM)
  • Specifying a schedule for that ecosystem
  • Setting up ignore rules, such as avoiding major version bumps
  • Defining groups to combine packages that have batched releases.

I'll walk through the different configuration options. At the end of the blog post, I have two examples: one for my blog and another for a Laravel application with a Vue.js frontend. I recommend reading the full documentation for the dependabot.yml configuration options, as I barely scratch the surface of my usage.

To configure Dependabot, you must have the dependabot.yml file in your .github directory.

First, all dependabot.yml files must start with the version key and contain an updates key. All of the package ecosystem configurations will go as an array under updates.

version: 2
updates:
  -

Now we can start flushing out the file.

Defining ecosystems for dependency updates

Since we're working with a Drupal site, we have Composer as a package ecosystem.

version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"

The directory key is required; Dependabot doesn't provide a default. This tells Dependabot that there is a composer.json and composer.lock file in the root of the repository (/).

More than likely, the Drupal theme uses tooling for CSS and JavaScript, so we need to add NPM as an ecosystem. In my experience, projects end up having the package.json in the theme directory rather than in the project's root.

version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"

  - package-ecosystem: "npm"
    directory: "/web/themes/custom/mytheme"

This assumes the package.json and relevant lock file (yarn.lock or package-lock.json) is located at web/themes/custom/mytheme. If you're using Yarn or pnpm over NPM, the ecosystem key is still npm.

Let's say your Drupal project uses GitHub Actions for its continuous integration workflows. If you're using any GitHub Actions, those are also versioned and must stay updated. We can add the GitHub Actions ecosystem for that.

version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"

  - package-ecosystem: "npm"
    directory: "/web/themes/custom/mytheme"
    
  - package-ecosystem: "github-actions"
    directory: "/"

Note, for GitHub Actions, you do not need to specify the directory as .github/workflows, only /.

Now, we'll begin to receive automatic updates for dependency! But, Dependabot will assign a random schedule for delivering them. Leading to chaos and a lot of frustration with earlier versions of Dependabot.

Specifying a schedule to more easily manage dependency updates

I like to receive my updates weekly. You can have it run daily, weekly, or monthly. Dependabot seems to have a limit on the number of updates it'll provide, so I wish you could configure specific days, like Monday and Thursday in case you have a lot of dependencies that may have been updated.

version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "weekly"

  - package-ecosystem: "npm"
    directory: "/web/themes/custom/mytheme"
    schedule:
      interval: "weekly"    

  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

By default, weekly updates are delivered on Monday but can be configured to a specific day of the week.

  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "tuesday"

Now, we've got scheduled updates to wrangle in dependency management chaos.

Setting up ignore rules to wrangle unwanted version bumps

What irritates me about Dependabot is how it will make a pull request to bump a dependency to a whole new major version when it does not match your semantic version constraints. For example, I had drupal/core-recommended set to ~9.5.0 in my composer.json. When Drupal 10.0.0 was released, I received a pull request bumping drupal/core-recommended to 10.0.0. That's not a valid update.

Luckily, we can use ignore to specify rules for ignoring specific dependencies or types of updates.

version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "weekly"
    ignore:
      - dependency-name: "drupal/core*"
        update-types: ["version-update:semver-major"]

This uses a wildcard to tell Dependabot to ignore major version updates for drupal/core, drupal/core-recommended, drupal/core-composer-scaffold, and drupal/core-dev. In the examples at the end, I also use the same for Symfony and Laravel.

Instead of using update-types you can also use versions to ignore specific versions. I don't recommend this for Composer dependencies, as you should use the conflict package links.

Now, let's group related updates to reduce pull requests for framework packages.

Grouping dependency updates

I was excited once Dependabot rolled out the grouped version updates. When working with frameworks that have multiple packages released simultaneously, it combines the updates into one pull request. This helps avoid spam when Drupal core has a release, and you have to upgrade drupal/core and drupal/core-composer-scaffold along with the metapackages drupal/core-recommended and drupal/core-dev. The same goes for other frameworks with various components that may be released simultaneously, such as Symfony or Laravel.

version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "weekly"
    ignore:
      - dependency-name: "drupal/core*"
        update-types: ["version-update:semver-major"]
    groups:
      drupal-core:
        patterns:
          - "drupal/core*"

We can use * for wildcards here, as well.

Examples

Here is the dependabot.yml for my Drupal site. I use the Gin theme, which has companion modules, Gin Toolbar and Gin Login, also released at similar intervals. So I grouped them as well.

version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "weekly"
    ignore:
      - dependency-name: "drupal/core*"
        update-types: ["version-update:semver-major"]
    groups:
      drupal-core:
        patterns:
          - "drupal/core"
          - "drupal/core-composer-scaffold"
          - "drupal/core-recommended"
          - "drupal/core-dev"
      gin:
        patterns:
          - "drupal/gin"
          - "drupal/gin_login"
          - "drupal/gin_toolbar"
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

Here is one from a Laravel application that uses a Vue.js frontend. I grouped the Symfony and Laravel updates and ignored major version updates.

version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "weekly"
    ignore:
      - dependency-name: "symfony/*"
        update-types: ["version-update:semver-major"]
      - dependency-name: "laravel/*"
        update-types: ["version-update:semver-major"]
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"

Here is my configuration file for drupal-mrn.dev, a monorepo containing a backend API and single page application frontend.

version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/api"
    schedule:
      interval: "weekly"
  - package-ecosystem: "npm"
    directory: "/api"
    schedule:
      interval: "weekly"
  - package-ecosystem: "npm"
    directory: "/app"
    schedule:
      interval: "weekly"
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

Want more? Sign up for my weekly newsletter

Jan 04 2024
Jan 04

Takeaway: Drupal’s comprehensive approach to multilingual features has made it a go-to solution for inclusive, global digital platforms.

Both Marco Angles and I have worked on Lenovo, with me focusing on content management and Marco focusing on development. Collaborating on multilingual projects, we both have witnessed the seamless integration of Drupal's capabilities in managing diverse languages.

Drupal’s intuitive system simplifies the process of updating and publishing content, making it possible to maintain brand consistency effortlessly across different language versions.

Marco started with Drupal 4.7, and over the years, he has seen its evolution into a powerful tool for building multilingual websites. Working on projects like Lenovo, Marco has helped with creating websites that not only speak multiple languages but are also brand-consistent across different versions of the site. 

Through my lens as a content manager and Marco’s insights on development, we'll explore how Drupal not only eases the management of multilingual sites but also ensures that these sites are accessible and easy to navigate for a global audience.

Lenovo Australia

The extent of Drupal's multilingual support

When working on the Lenovo site, I’ve observed Drupal’s proficiency in handling over 100 languages. This capability is not just theoretical but practically implemented in my work, reflecting in the seamless management of diverse language content and ensuring brand consistency across various regions.

Drupal's ability to support a wide range of languages, including complex ones like Chinese and Arabic, has been crucial in projects where the website serves different regions. This isn't just about adding languages—it's about creating a platform that can adapt to the linguistic intricacies of each region.

For example, in the URL, you'd have domain.com/gb/en for Great Britain in English. You can have different combinations like region/language. So, for France, it might be fr/fr, or for Belgium in French, it would be be/fr. This kind of region/language specificity is particularly tailored for Lenovo.

And although they say it supports 100 languages, Marco explains he’s confident that you can add custom languages if needed. For instance, in Lenovo, the development team created a language we call “Worldwide,” which is unique in the URL structure.

Aside from Drupal being able to show various languages on the front-end, content editors and developers who aren’t English speakers are also able to change the language on the back-end, making it easier to work on the site as a non-English speaker.

Interface text language detection

Our experiences highlight Drupal's robustness in offering extensive multilingual support. It's more than just about the number of languages—it's about ensuring each language version functions effectively, aligning with user expectations. Drupal stands out in its ability to manage and present multilingual content cohesively, making it an invaluable tool for websites with international users.

Maximizing Drupal’s multilingual features

For a small company with limited resources, adding Google Translate to a site might be more practical since it requires creating content only once. But for a company like Lenovo that wants full control over their content, having separate forms for each language is essential.

Marco explains more in the next section.

The technical side of Lenovo’s multilingual capabilities

For building a website like Lenovo's, it starts with installing modules. While Drupal core itself has robust translation capabilities, there are also additional modules you might need, depending on the website's requirements. The key modules for translation are:

We use several custom modules at Lenovo, although other sites might not use as much. For layout builder, there's another separate module. Regarding translation management, we mainly use the core Drupal features.

We don't use the Locale module; instead, we utilize a contributed module from Lionbridge. It's a third-party service with a complex system for translating content, different from Google Translate. Google Translate works on-the-fly, translating what's presented on the website. In contrast, Lionbridge focuses on translating specific fields and content in the backend.

Lionbridge translation company

For example, when a page is created in English, it is sent to Lionbridge with a request to translate it into Japanese. They translate it using their system and then send the translated content back. The page on the site is then updated with this real, translated content, rather than on-the-fly translations like Google's.

Does it take longer? It does, so we’re still working out the kinks. For instance, if you plan to publish a page in a week, you need to factor in the time for translation. It's a different setup from on-the-fly translations, and clients typically manage this timing themselves before publishing the pages.

On hreflangs and canonical URLs

The hreflang attribute is automatically generated when you create a language. For example, if it's ww/en, it will create the lang attribute accordingly. As for canonical URLs, we have an automated system for that too. It's pretty straightforward—the system automatically determines the language and provides the canonical URL for it.

Drupal's multilingual management is mostly automatic. We don't rely heavily on contributed modules for translation. There are some, but generally, the core functionality is robust and powerful. Once you understand how it works, managing translations isn't difficult.

Other Drupal tools and features for managing multilingual content

Drupal's core offers solid translation capabilities, so that should be your first go-to. If those aren’t enough, there are various contributed tools available on the Drupal website that can make life easier, depending on the client's business model and specific needs.

For instance, if a client wants their website to be visible in Japan but doesn't have anyone who speaks Japanese, they might require more tools than what Drupal provides out of the box. Each context and business model is different, but generally, Drupal's content translation system is straightforward once set up.

One of the tools that makes life easier for users is the localization feature, where the interface is translated. For instance, at Lenovo, all the editors speak English, so we don't need to localize the interface. Everyone understands English. However, if we had a Japanese team that didn't speak English, we'd have to take care of localizing the interface for them.

And because the Drupal community has a team of translators, the translations present in Drupal are robust. For example, let's say there are 950 pages of strings. You can simply create all the translations.

How? You select the language you want to translate into, save it, and then the strings get translated. It’s easy because there is a way to import what has already been translated by the Drupal community's team of translators. You can also export and adjust settings as needed.

Translation vs localization, and working with a third party

Translation and localization for Drupal are two different things. Translation is about making the page available in other languages, but the source language remains the same. Localization is about matching existing translations to ensure they match.

Translation for Drupal

For instance, if the source is English, it remains in English, and we translate it to other languages as needed. Let's say we're dealing with content for the Netherlands or Belgium:

  1. We manually extract the content.
  2. We send it to a translation company.
  3. We sync the translated content back to our CMS.

It's not an automatic process for every language, at least because we’re not just using Google Translate on the website.

While we do have an automatic translation feature for users who want to view content in English, creating content in local languages requires manual intervention. As mentioned earlier, we work with Lionbridge, our translation company, to manage this. They're integrated into our CMS, and this is how we handle translations for different regions.

Requestion translation

Currently, we're working on submitting translations through a portal. Lionbridge receives the job requests and handles the translation. We're still relying on the manual process of sending translation requests.

Another translation method according to Marco, involves activating languages in the translation settings.

For example, you might activate English, French, and Spanish. If you enable the right module and set English as the default language, when you create a node, it initially creates the content in English. After entering your content and saving it, you'll see a “translate” tab at the top of the page. Clicking on this allows you to create versions in other languages, like French.

When you select to create the French version, you'll get a new form for entering the French content, which can differ from the English version. It doesn't have to be a direct translation. This method also lets you decide whether each field in the content is translatable.

For instance, if you don't want a taxonomy term to be translated, you can set that field as non-translatable, and it will remain the same across all language versions. Conversely, if you make it translatable, each version can have a different term attached.

This applies to any field in Drupal and is my preferred method for creating a robust, translatable website, a feature not commonly found in other CMS platforms.

Localization for Drupal

For individual pages, we manually localize content using an Excel file provided by Lionbridge, which contains the specific translations needed for that page or language.

Usually, localization involves cultural research. But since we already have translations provided, we don’t need to do that.

The manual localization is quite straightforward:

  1. We have the English content.
  2. Lionbridge provides us with the localized version.
  3. We compare and ensure the translation matches the original content. 
  4. For content embedded in images, we need developer assistance, as this often requires additional skills like HTML or CSS.

Advantages and challenges of multilingual websites

One major advantage is the ability to access pages in native languages. Lenovo has a language selector on the upper right side of their pages, allowing users to easily switch to available languages.

This feature is particularly appealing to our clients, as it negates the need for tools like Google Translate.

And although there is a need for more editorial manpower to manage content across multiple languages, at least for sites that cater to numerous regions, there are plenty of resources and documentation available on the Drupal website to guide through these processes.

Lastly, Drupal is very accommodating for multilingual websites. You could have, for example, an English site managed by a US team and a German version managed by a team in Germany. This is feasible because Drupal allows multiple teams to work on different language versions.

The advantage of Drupal is its robust structure for translation, which has been a core feature since its inception.

Lenovo Argentina

There are still challenges we need to grapple with.

For example, if a specific page isn't localized for the visitor's country, then there's no translation available. This issue can arise with disabled languages or with content and products not offered in certain countries.

Another challenge is the use of specific words. Some words are not acceptable in certain countries, and the way we set up the Lenovo site automatically replaces them. This can be a limitation when stakeholders request specific wording, but they are generally aware of these restrictions and we find alternative expressions.

Lastly, if your site needs a huge number of languages can be a challenge. According to Marco, the need for manual checks increases with each additional language. If you're a small company with limited resources, using on-the-fly translation services like Google Translate might be the best option.

Ensuring brand consistency in multiple languages

When we localize or translate a page to other languages, we aim for it to be a mirror image of the original. However, there are exceptions. For example, if a specific Lenovo product isn't available in a region, we won't localize that page for that region, or we'll remove components from the page that feature that product.

Here are the steps we take:

  1. When adding something to a page, we first reach out to all regions to ensure it's appropriate. For instance, our worldwide team creates the source pages.
  2. Once a source page is created, our client communicates with all regions to see if they want the page featured in their area.
  3. Sometimes, regions discuss the relevance of a page even before it's created, to determine if it will be adopted or not.
  4. The localization process begins once there's agreement.

Currently, some countries are still using older pages and are slowly transitioning to the new ones we've developed. Our move away from the basic page format to the newly built layout builder pages is because they're more visually appealing and easier to edit.

Editing time has significantly decreased with these new pages. Previously, editing a single component could take up to three hours, but now it's much faster. Our developers have put considerable effort into enhancing the efficiency of the layout builder.

To make sure everything is consistent across regions, communication is key. For example, we recently got a request from AP (Asia-Pacific) to remove a specific component from a page. Our colleague who was in charge responded by asking for the reason behind this request since removing an element from a page can affect all regions, as each version needs to mirror the others.

There are also instances where we promote certain items on our pages. We create specific components for these promotions. However, sometimes these promotions don't go as planned, and we're asked to remove or take down these components.

In such cases, it's crucial to inform other regions immediately. This helps ensure that all versions of the page align with the set timelines for when a promotion is supposed to start or end.

Ensuring accessibility in a multilingual website

Accessibility is one of Promet’s top priorities, so naturally we had to work to ensure Lenovo is accessible as well.

As a content manager, I conduct daily random checks of our pages. Given the vast number of pages, it's challenging to check all of them, but the other content manager and I strive to review as many as possible, especially after weekly deployments on Thursdays.

Here are the issues we typically check for:

  • Before publishing any content, I always have it proofread by someone else to ensure there are no grammatical errors or incorrect punctuation.
  • We proactively check for issues like images not rendering correctly or outdated content.
  • We also look out for typographical errors, which can occur, particularly when local editors make edits.
  • Post-deployment, we ensure nothing is broken, as code changes can sometimes affect other elements.
  • When rendering or other page issues happen, we investigate the cause.

Our goal is always to catch and correct these issues before our clients notice them.

The developer’s role in ensuring accessibility

According to Marko, there's a distinction between the developer's responsibilities for site markup and the content editors' role.

The developers are responsible for ensuring the site's markup is accessible. However, editors have significant freedom in content creation, which can potentially lead to non-accessible content.

So, developers try to limit editors' freedom by adding filters to the content creation process, thereby maintaining a certain standard of accessibility and user-friendliness.

Lenovo content editor

For example, let’s say a content manager creates a basic page, and within the editor, there's a body text field that appears on the page once filled. Lenovo wanted the freedom to create content, so they have a lot of options in their editor.

However, generally, developers give editors very limited access to creating markup to ensure accessibility.

Another accessibility feature we have is the ability to add translated alt texts to images as well. This ensures that when a page has a translated version, the users viewing that page would hear the alt text in the translated language as well.

Image alt texts translations

How shifting servers helped with accessibility and UX

We're also constantly working to enhance our CMS's performance. For example, we've significantly reduced page loading times. It used to take 30 seconds to a minute to access a page, but now, thanks to our shift to Redis, it can take as little as 4 to 10 seconds.

We've been using this server since November 2022 and have seen significant improvements in speed after switching. This change was especially crucial during campaigns when the Lenovo site visits could hit 30,000 a day.

Before Redis, higher traffic significantly slowed down the site, leading to customer loss. Redis has reduced editing time for components from minutes to just 10 to 30 seconds. This efficiency is beneficial, especially since some regional teams bill by the hour or minute.

With a faster CMS, our client's costs are lower, and the regions have noticed improvements in page load times. Andy, Promet’s president, was instrumental in testing this, confirming the enhancements. This improvement has been a major focus for us.

Transform your website into a multilingual platform with Drupal

We underscore Drupal's strengths in creating inclusive, multilingual websites, highlighting Drupal's flexible system that’s capable of handling diverse languages which is vital for global reach.

Our experiences reflect Drupal's ability to maintain brand consistency while adapting to local contexts, ensuring a seamless user experience across different regions. This adaptability, coupled with strong support for translation and localization, positions Drupal as a powerful tool for building accessible, multilingual digital platforms.

Reach out to our Sales team today and get your website transformed into a multilingual powerhouse.

Jan 04 2024
Jan 04

Online payment processing is probably one of the most important aspects of your ecommerce website that should provide flexibility and security. In this post, we’ll try to give you a brief introduction to online payment services. We’ll delve into the terminology, track money on its way from the buyer to the online store account, and warn you about the possible dangers and expenses.

And surely, we wouldn't be a Drupal web studio if we didn't cover the issue of online payments on a Drupal website.

Introduction to Online Payment Processing

Electronic Payment System

An electronic payment system (EPS) is a combination of physical devices and programs working together so that instead of a wallet stuffed with bills you could use its electronic — and, therefore, much more efficient — equivalent: a plastic card (Visa, Mastercard), its virtual copy, or a web wallet (WebMoney, PayPal, AdvCash). EPSs allow you to pay for your purchase with a credit card in a physical store or by using payment details on a website or in an app, pay your utility bills, or lend money to a friend without face-to-face contact.

Payment processors or payment service providers are also classified as EPSs. They act as intermediaries for the buyer, the bank, and the seller and provide a whole range of services in exchange for a certain percentage of the transfer amount. Say, when remitting money from the card of X bank to e-wallet Y is impossible or associated with extra charges, the aggregator takes on the task of an envoy of sorts. Each payment processor is convenient and inconvenient in its own way, so you should select one according to your reality, particularly, depending on the country of your business.

We have experience with such payment processors as Stripe, PayPal, Braintree, Square, Worldpay, and Ubercart.

Internet Acquiring

This term implies the ability to make online payments without producing a physical card. The user only has to type in its details on the website and comfortably click on the “Complete online transaction” button. After that, the famous magic we’ll talk about below sends your money to the ecommerce site. The card as a piece of plastic is of no use now, that’s why you can maintain a virtual card from your bank or a web wallet.

Payment Gateway

To put it tentatively, this is a channel used to send the encrypted number, date, and CVV of the buyer’s card. This is done using an intricate but safe route where nothing must happen to the data.

Frequently used payment gateways include Authorize.net, Amazon Payments, WePay, 2Checkout, Dwolla, and others. Sometimes, payment platforms take on the tasks of encryption and data transfer between the transaction parties — such functionality is available, say, in Stripe, PayPal, and Worldpay.

Jan 03 2024
Jan 03

Over the past 12 months, our teams have completed numerous Drupal upgrades. We would like to share our experiences and knowledge with anyone who has yet to undergo this process to help make it smoother for you.

Drupal Core Major Upgrades Blog 1

Background

From Drupal 8, upgrades became a lot easier with several tools to semi-automate the process.

In addition, if you, like CTI Digital, have a number of sites to upgrade and different developers working on each of them, you could easily duplicate effort as many contributed modules are used in most sites - e.g. Webforms and Paragraphs, just to name two. Therefore, this article also contains the approach that CTI Digital took in managing the latest upgrades to Drupal 10 of around 50 sites (some still on Drupal 8) to ensure knowledge transfer within the team.

This article looks at the tools available and the lessons we learned.

Why is it easier since Drupal 8

The approach taken by the Drupal core team with Drupal 8+ has made things a lot easier. That approach is to deprecate hook functions, classes, class methods, etc., while introducing their replacements. Then, a list of deprecated features that will be removed in the next major version is produced and fixed at a specified minor version of the current Drupal major version (8.9 for D8 and 9.5 for D9). Drupal's main dependency, Symfony, takes the same approach.

This approach clarifies what is deprecated in Integrated Development Editors (IDEs) and allows various tools to semi-automate the process. It enables them to find deprecated code, advise on what changes are needed and even make many of them. Finally, the Drupal CI systems automatically run tools to spot deprecated code on contributed modules and produce patches, which are then attached to a new issue created for each module. These can then be tested, modified and approved by the community.

Tools 

So, what are these tools? There are command line tools for picking up and making changes, but there are also contributed modules (often using those command line tools). The ones we at CTI Digital found useful are listed here.

The three tools we used are:

PHP Codesniffer - a tool to find code issues by rule; in this case, PHP 8.1 compatibility

Drupal Core Major Upgrades Blog 2

Upgrade Status

This is the first one that should be installed on a development copy of each site to identify the work that needs to be done. It is a contrib module that can be installed with composer:

composer require drupal/upgrade_status

This tool provides a report through your site's UI (Reports/Upgrade status admin/reports/upgrade-status). This report details everything you need to do - down to lines of code that need changing per module or theme. This detail includes what to change each line to and if Rector - see next section - can make the change automatically.

It will tell you the following: 

  1. Environmental factors - such as PHP, database and Drush versions required. For Drupal 10 it will also include a list of invalid permissions and deprecated core modules that you have installed

  2. Modules/themes that are not installed

  3. Contributed modules/themes that have an upgrade available (including if that is compatible)

  4. Contributed modules/themes that have nothing available (with a link to an issue queue search for issues containing the text ‘Drupal 10’)

  5. Modules/themes that have changes that could be made by Rector

  6. Modules/themes that have changes that need to be fixed manually

Each module/theme can be scanned for detailed depreciations, which will also categorise each as one that can be done with Rector or one that needs to be done manually. This can be run where there is not a compatible version or patch already available.

For all contributed modules/themes, you have the following options:

  • Upgrade to a more recent version

  • Apply a readily available patch on its issue queue

  • Upgrade and patch

  • Write a patch from scratch (using Rector where possible)

  • Remove an unused module/theme from the code

  • Replace the module/theme

    It should be noted that removing or replacing a module/theme that is currently installed requires two deployments. The first uninstalls the module/theme, and the second removes it from the code base. If you do both in one go, you will get issues with Drush commands in the deployment.

Drupal Rector

This command line tool can automatically make many of the necessary changes. It can be installed in a project with:

composer require palantirnet/drupal-rector --dev

Then, a file called rector.php needs to be copied from the vendor/palantirnet/drupal-rector folder to the Drupal root directory (web in the examples here).

You can then run this tool on any module/theme with:

vendor/bin/rector process web/[modules or themes]/[SUB_FOLDER]/[YOUR_MODULE] --dry-run

This will find what needs to be changed, and if you are happy with the changes, removing the –dry-run option will, of course, allow it to do its thing.</span>

PHP Codesniffer

This command line tool is not specific to Drupal core upgrades. It looks for particular code patterns, and you can install add-ons to look for anything, including PHP versions. Since Drupal upgrades often include PHP upgrades, it is at least worth running this tool on all custom code.

In order to test for PHP 8.1 ( required for Drupal 10), the following will install the tools you need:

composer require --dev phpcsstandards/phpcsutils:"^1.0@dev"

composer require --dev phpcompatibility/php-compatibility:dev-develop

You can confirm that this worked by running:

vendor/bin/phpcs -i

This command will list what sniffs are installed and will include PHPCompatibility.

Then the following can be run to test all your custom modules:

vendor/bin/phpcs -p web/modules/custom --standard=PHPCompatibility --runtime-set testVersion 8.1 --extensions='php, module,inc,install,theme'

This will test for PHP 8.1 compatibility, specifically in all PHP code files. You can do the same for any custom themes.

Drupal Core Major Upgrades Blog 3

Deprecations not spotted by the tools

There are some deprecations that the tools do not spot. They are services, libraries and hook functions. In the case of services, these are the calls to \Drupal::service(''). If this call is assigned to a variable and that variable is given the relevant class (/* @var */), then that class will also be deprecated and picked up. Also, if you inject a service into a class, the service's related class will be picked up.

The only solution we found was to create a text file with one service, library or hook function per line and use grep to search the custom code:

grep -r -f [textfile] web/modules/custom

And the same for custom themes.

The upgrade steps

Apart from upgrading and patching contrib modules and themes, fixing custom modules and themes, and removing unused code, there are two extra steps for D9 to D10 upgrades.
The first is to fix an old issue of invalid permissions. In D8, modules/themes were not required to delete permission allocations to user roles when they deleted a permission that they created. One of the minor versions of D9 firmed this up, and a core database update removed any such orphans. However, this did not always work, and the Upgrade status report lists the user roles with non-existent permissions that must be deleted - manually from the configuration yml files.

The second is core modules and themes that have been deprecated and are being deleted from D10. The Upgrade status report will list the core modules and themes the site uses. All of these have a contributed version that can be added to a project if needed. Detailed recommendations are also available here.

The final upgrade to core

This should simply be a case of running the Composer command once everything else is done:

composer require drupal/core-* –with-all-dependencies

This presupposes that the site is built with the standard drupal/core-recommended and related packages.

However, you will often find that Composer finds some requirements clashing with D10 or its dependencies.

The main reason is that a module/theme is patched to D10, including the change to the info.yml file's core_version_requirement and the composer.json file. Composer will have an issue with this. This is because Composer is using the files in the repository to determine compatibility, not the patch file changes. However, there is a solution with 'lenient'. This Composer add-on allows you to ask Composer to be lenient on the constraints of individual packages.

The command to add the lenient package to the site is:

composer require mglaman/composer-drupal-lenient

The command to add a list of modules and themes to the allowed list is:

composer config --merge --json extra.drupal-lenient.allowed-list '["drupal/YOUR_MODULE1", "drupal/YOUR_MODULE2" …]'

In addition, some projects have drupal-composer/drupal-scaffold as a dependency. This is deprecated, and you will get a notice about that. It is to be replaced with drupal/core-composer-scaffold. Finally drupal/console is incompatible with D10 and needs to be removed.

Managing upgrades for a large number of sites

As mentioned at the top of this article, there could be a lot of duplicate effort and different approaches taken if steps are not taken to organise the upgrade of a large number of sites.

The solution we at CTI Digital came up with is simply maintaining a spreadsheet of information about contributed modules/themes. This would contain recommendations (version, available patches) and any complications or special instructions associated with that module or theme's upgrade, etc. It is maintained as more sites are audited and then upgraded with findings as we go to assist later sites. This can also be applied to any custom modules/themes that are shared with more than one site.

As each site is audited for the effort required, the spreadsheet is referred to and added to so that effort is not duplicated and the spreadsheet is kept up to date with findings from each site. Also, any approaches that should be followed on all sites are kept here - e.g. we replaced all sites using Swiftmailer with Symfony Mailer for mail management.

The other primary approach was to consolidate effort patching contributed modules/themes that are not ready. CTI Digital's work to improve a contributed module/theme is always contributed back to drupal.org. A link to this new patch is stored in the database for the following site that uses this module/theme.</span>

4-Jan-02-2024-12-30-44-3036-PM

What about Drupal 8 to Drupal 10

CTI Digital still had a few sites on Drupal 8, which needed to be upgraded to Drupal 10 rather than two separate upgrades.

There are factors that affect this:

  • Upgrade status will only give what needs to be done to get to D9

  • Drush does not cope with a deployment that goes from 8 to 10 in one go

  • A significant number of contributed modules/themes have versions that are 8 and 9 compatible and versions that are 9 and 10 compatible, rarely one version compatible with 8, 9 and 10

  • A few contributed modules/themes have the newest version drop support of D9

    This means you have to do an 8-9 audit (with the dev copy on D8.9 minimum), upgrade the dev copy to D9 and then do a new audit from 9-10. You will be able to assess the complete upgrade requirement for all the modules/themes identified by the first audit, but the second audit will find modules/themes currently compatible with D9 but not D10 that are missed by the first. The second audit will also supply invalid permissions and core modules/themes that are removed from D10.

    You must also upgrade as two deployments minimum (one to D9 and the second to D10). Some modules/themes will be upgraded in the D8 site before upgrading to D9, and some in the D9 site before upgrading to D10. Some will have to be upgraded twice. Some will need to be upgraded when the site is on D10 (occasionally at the same time as the core upgrade). It will depend upon the module's upgrade path and the version the D8 site is on.

What is next?

You may be wondering if anything can make things easier for D11. Although the overall effort can not be reduced, it can be spread out.

If you regularly upgrade contributed modules and themes to the most recent version, you will reduce the work when it's time to upgrade to D11.

If you also run regular Upgrade Status reports on your D10 site, you can create work dockets for changes to your custom code. It can also be used to identify contributed modules and themes that you use that have not yet been upgraded. You could create work dockets for replacing these modules or contributing patches to upgrade them to D11. These can be spread out between now and the need to upgrade to D11.

The ultimate goal of these approaches is that you only have to upgrade the core when it is time to upgrade to D11.

To get ahead, speak to one of our Drupal experts and book a 30-minute session today to discuss a migration or upgrade for your business. 

Jan 03 2024
Jan 03

Introduction

Salesforce Data Cloud helps organizations unify customer data from various sources, analyze it to gain valuable insights, and personalize experience across channels. It helps maximize ROI and boost operational efficiency.

The recent announcement in Dreamforce 2023 has also clarified that Data Cloud is the future foundation for data in Salesforce. It will power everything from the system of record for customer data to enable customer engagement within different industry clouds.

Data Cloud makes bringing whatever data you want into Salesforce easy. By unifying and harmonizing that data, it creates a golden record that contains all the information about your customers, your orders, cases, vehicles, or whatever entity you choose.


Steve Fisher, EVP & GM of Next Gen CRM & Unified Data Services @Salesforce

What Is Salesforce Data Cloud?

Salesforce Data Cloud is a comprehensive platform that helps organizations manage customer data at scale. It ingests data from multiple sources, matches customer records, maintains accuracy, and makes unified customer profiles for analytical, marketing, service, and sales use.

Data Cloud also enables real-time segmentation and activation to optimize customer engagement. Some of its key capabilities are:

  • Connects data from CRMs, web apps, mobile applications, loyalty programs, and email platforms
  • Resolves customer profiles across data sources through probability matching
  • Maintains accurate customer profiles with consistent data quality checks
  • Builds unified customer profiles with analytical attributes
  • Segments customers and activates marketing campaigns based on user behavior
  • Generates actionable insights from customer data by leveraging AI

Data Cloud is built using Salesforce metadata, ensuring that the data stored in the Cloud is visible and available in Customer 360. Data also goes through several stages while being processed within the Data Cloud.

A Short Tale: How Williams-Sonoma, Inc. Nurtures Customer Relationships With Salesforce Data Cloud

Williams-Sonoma, Inc. has nine brands that are household names, including Pottery Barn, Pottery Barn Kids, PBteen, West Elm, and Williams Sonoma Home. Each of these brands serves a different audience that requires more than a tailored message to build lasting customer relationships.

By leveraging Salesforce Data Cloud, Marketing Cloud, and Experience Cloud, the organization has been able to:

  • Deliver personalized messages that customers look forward to
  • Launched several targeted marketing campaigns
  • Provide faster support with efficient sales and service
  • Utilize AI to automate tasks and centralize work

This has led to a growth of 21 million subscribers, maximizing employee performance, maintaining stability, and accelerating time to value.

Benefits Of Salesforce Data Cloud

Salesforce Data Cloud helps organizations boost ROI, operational efficiency, and achieve other benefits like:

Salesfroce Data Cloud Benefits

 

Generate Comprehensive Customer Insights

Data Cloud can construct a cohesive 360-degree customer data perspective from diverse origins. The simplified access and centralization of customer information empower organizations to base decisions on data, implement personalization strategies, and enhance sales and marketing initiatives, ultimately leading to a higher ROI.

Enhance Personalization

Data Cloud uses enriched customer profiles and behavioral data to facilitate precise audience segmentation. This helps run targeted marketing campaigns, personalized recommendations, and customized messaging, leading to heightened customer engagement, increased conversion rates, and higher ROI.

Optimize Sales & Marketing Strategies

Salesforce Data Cloud helps organizations get detailed insights into their sales and marketing initiatives. By scrutinizing customer behavior, evaluating campaign effectiveness, and identifying high-value leads, businesses can optimize resource allocation, refine sales pipelines, and enhance the efficiency of marketing expenditures.

Streamline Workflows

Salesforce Data Cloud boasts robust data management features, including integration, cleansing, and deduplication. These features streamline data processes and workflows, minimizing manual efforts, reducing errors, and enhancing data quality and accuracy. It also saves time and resources, allowing teams to redirect efforts toward high-value tasks and strategic initiatives.

Boost Productivity

The automation capabilities embedded in Salesforce Data Cloud allow organizations to automate repetitive tasks and streamline operations. Automation of data synchronization, updates, and reporting processes enhances productivity, mitigates manual errors, and saves resources for strategic activities, contributing to improved operational efficiency.

Robust Scalability

Data Cloud utilizes its capabilities to handle substantial data volumes and accommodate the growth of customer interactions. This helps ensure that organizations can scale their operations seamlessly without compromising efficiency.

Proactive Decision-Making

Salesforce Data Cloud helps organizations with predictive analytics, enabling them to forecast trends, identify potential risks and opportunities, and make proactive decisions. Leveraging these insights allows organizations to optimize inventory management, anticipate customer needs, and take preemptive actions.

Salesforce Data Cloud Use Cases For Higher Ed Institutes & NGOs

Salesforce Data Cloud is a comprehensive platform providing organizations access to high-quality, up-to-date data. It offers a vast repository of information from various sources, including business and contact data.

With Data Cloud, higher education institutes and NGOs can enrich their existing data and build a more robust, accurate picture of their donors, students, and constituents. Some other use cases are:

  • Data Enrichment: Salesforce Data Cloud allows organizations to enhance their data with valuable information. This means better donor profiling and segmentation for NGOs, while Higher Eds can improve student engagement and enrollment strategies.
  • Improved Decision-Making: With access to accurate and real-time data, NGOs can make more informed decisions about fundraising campaigns, program effectiveness, and resource allocation. Higher Eds can use this data to optimize student recruitment, retention, and academic planning.
  • Targeted Outreach: Salesforce Data Cloud enables NGOs to identify potential donors most likely to support their cause. Higher Eds can use it to target prospective students based on their academic interests and demographics.
  • Data Quality Assurance: Data quality is crucial. Salesforce Data Cloud helps organizations maintain data accuracy and consistency by continuously updating and verifying records, reducing errors and duplications.

Practical Examples For Higher Eds

Data Cloud can help higher education institutions ensure:

  • Student Recruitment: Higher Eds can use Salesforce Data Cloud to refine student recruitment strategies. By analyzing demographic data and academic interests, institutions can identify and target prospective students who better fit their programs.
  • Alumni Engagement: Maintaining strong relationships with alumni is vital for higher education. Salesforce Data Cloud enables institutions to keep alum data up-to-date and identify opportunities for engagement, such as reunions or fundraising campaigns.
  • Academic Advising: By enriching student records with data on past academic performance and extracurricular activities, Higher Eds can provide more personalized academic advising, helping students make informed decisions about their courses and career paths.

Practical Examples For NGOs

Data Cloud can help NGOs ensure:

  • Donor Relationship Management: NGOs can use Salesforce Data Cloud to enrich donor profiles with additional information, such as occupation, interests, and giving history. This data can help tailor communication and fundraising efforts, resulting in increased donations and donor retention.
  • Grant Opportunities: Salesforce Data Cloud can be leveraged to identify potential grant opportunities by tracking funding trends, grantor preferences, and deadlines. NGOs can target their grant applications more strategically.
  • Event Planning: When organizing fundraising events, NGOs can use Salesforce Data Cloud to identify and invite donors who are most likely to attend and contribute. This targeted approach maximizes event ROI.

How To Set Up Salesforce Data Cloud

Follow the steps given below to enable Salesforce Data Cloud.

Step 1: Send eSignature Contract

Go to your account and click on ‘Browse and Buy.’ Scroll down the list of products to find Salesforce Data Cloud. Click ‘Add to Cart.’

Users can sign in via email, where they will be asked to proceed with a zero-amount order form/contract. Send your eSignature contract to proceed.

esignature contract from Salesforce

Step 2: Assign Permissions

Next, users will receive an email with a ‘Welcome to Data Cloud’ subject line. Two types of permission sets will become visible when Data Cloud is provisioned in the organization, which can take anywhere from 30 minutes to 24 hours.

  • Data Cloud Admin, which gives access to Data Cloud Setup
  • Data Cloud Users

Go to Salesforce Setup and search for ‘Users’ to add the permission sets to user records. On the user record, hover over the ‘Permission Set Assignments’ tab and click ‘Edit Assignments.’ Move the Data Cloud Admin permission set from the left to the right list.

Permission_sets

Step 3: Set Up Data Cloud

Users can see ' Data Cloud Setup ' under the ‘Salesforce Setup’ option. Click on it and land on the guided setup page.

Set up option

Each item will be checked off automatically when the user starts the process. The entire process will take around 15 minutes.

Data cloud setup

Step 4: Connect Data Sources

Next, users will be guided to a page to connect Marketing Cloud with Data Cloud. Organizations can also connect to other data sources like Salesforce CRM and B2B Commerce.

Step 5: Assign Permissions To Data Cloud Users

Go to the ‘Salesforce Setup’ option and search for users. On the user record, hover over the ‘Permission Set Assignments’ tab and click ‘Edit Assignments.’ Move the Data Cloud user permission from the list's left to the right side.

To add permissions for a few users at a time:

  • Go to ‘Salesforce Setup’ and search for permission sets.
  • On the ‘Permission Set’ record, click ‘Manage Assignments.’
  • Move the relevant users from the left to the right side of the list.

Permission set assignments

Administrators can also add permission sets to a profile to manage permissions for multiple users with the same profile. In this case, start by pulling a use report of all users with a particular profile, including the User ID field.

Export the report and change the User ID header to Assignee ID. Create another column for the Permission Set ID. Next, go to the permissions set in Salesforce and copy the ID from the URL. Paste the Permission Set ID into the correct column of the exported sheet. Use the Data Loader to do a 'PermissionSetAssignment' insert.

How Salesforce Uses Data Cloud To Fuel It’s Business

Salesforce leverages the power of Data Cloud to drive its business to new heights. By harnessing vast data sources, Salesforce gains valuable insights into customer behavior, market trends, and business intelligence.

This strategic use of data enables Salesforce to enhance its products, personalize customer experiences, and optimize operational efficiency. The Data Cloud empowers Salesforce to deliver cutting-edge solutions, improve decision-making processes, and stay at the forefront of innovation.

Through this comprehensive utilization of data, Salesforce strengthens its internal operations. And consistently offers its users a platform that evolves with the ever-changing demands of the business landscape.

The experts at Axelerant can help you get similar benefits by harnessing the full potential of data-driven insights. Schedule a call to learn how.

Jan 02 2024
Jan 02

In this article, we focus on tackling the organizational challenges that come with the technical realities of the migration away from Drupal 7 to newer, up-to-date versions of the content management system​​. We outline a strategic blueprint for a successful Drupal 7 migration, centered around three critical audits:

  • Content audit: Evaluating which content should be carried over to the new platform.
  • Design audit: Seizing opportunities to enhance the site’s design during the rebuild.
  • Technical audit: Refining the site architecture and meticulously planning the technical aspects of the migration.

This is the perfect catalyst and opportunity for your organization to assess and not just transition, but to reconstruct your digital presence to meet your future goals and challenges.

As the clock ticks towards January 2025, the End of Life (EOL) for Drupal 7 looms on the horizon. Moving away from Drupal 7 marks a critical juncture. Since Drupal 8 and above are significantly different in architecture from Drupal 7, the move requires a comprehensive migration. The good news is that the upgrade paths from Drupal 8 and above are smoother, so the step up from Drupal 7 represents a unique, one-time effort, which will pay itself off in longer site life cycles and higher ROI on that investment.

The core principle guiding this migration strategy is the alignment of technical implementation and design with the initial content audit. This approach ensures that the rebuild is driven by content needs, rather than forcing content to conform to a predetermined site structure.

Some key organizational challenges issues when navigating this technical shift away from Drupal 7 revolve around governance, starting by understanding the existing content on your website, assigning responsibility for it, and ensuring its relevance and quality throughout the migration process.

This technical inflection point can and should also spark a broader debate about the extent of redesign and transformation needed during the migration. IT and marketing teams should be discussing, in a nutshell, “What do we have? What can be better? What do we need to make that happen?”

Palantir.net is a Drupal Association–Certified Migration Partner. We have the technical expertise, experience, and strategic insight required to help steer you through this vital transition. Whether through our Continuous Delivery Portfolio (CDP) or web modernization services, Palantir is ready to assist you in navigating the complexities of the D7 EOL upgrade.

Content audit: Decluttering the house

At the heart of any successful migration lies a well-executed content audit, a responsibility primarily shouldered by the Marketing team. This vital process streamlines the migration by identifying what content truly needs to be transferred to the new platform and what can be retired.

The essence of a content audit

The key questions to address during the audit are: What content do we need? What can we do without? These decisions should be data-driven, relying on analytics to assess the relevance and usage of the content. Key metrics include page views, content validity, and user engagement.

If you don’t like having a cluttered house, you don’t just decide to build a slightly bigger house, then move all your clutter into it. It would be a better idea to take a look at what’s actually cluttering your house first, then decide what you need to build. In the same way, letting content drive your technical decisions is the better approach.

The complexity of a given migration is often more dependent on the variety of different content types than the volume of content. Once a system for migrating a specific type of content, like blog posts, is developed, it can run autonomously while the technical team focuses on other content types. For this reason, a site with numerous content types requires a more intricate migration plan compared to one with fewer types. A content audit can help reduce the number of content types and with it the resulting effort needed.

Tips for conducting a successful content audit

 The following considerations can help make your content audit a smooth and effective process:

  • Develop a comprehensive content inventory: Start by cataloging every piece of content on your website. This step is crucial as it allows you to see the full scope of your existing content and understand what requires improvement, discarding, or migration. Document key details of each content piece, such as URLs, titles, purpose, format, number of internal links, and other relevant information.
  • Make data-driven decisions: Use tools like Google Analytics and Google Search Console to review content performance, examining metrics like traffic, engagement, bounce rates, time on page, and conversion rates. This quantitative analysis helps inform your content strategy and guides decisions on what content needs updating or removal.
  • Complement data with qualitative decisions: Compare your content against benchmarks that align with your goals and audience needs. Assess the content for user experience, compliance with your style guide, and potential improvements. Decide on actions for each content piece, such as keeping, updating, consolidating, or removing, based on their relevance, quality, and performance.
  • Involve a content strategist: An expert content strategist can help with all the above tasks and aid you in preparing a migration framework. They will help align your content with your marketing and branding goals, as well as UX design and information architecture. If you don’t have an internal content strategist, Palantir can provide one if we help you with your migration. 

Content as opportunity for a redesign

Conducting a content audit does more than just streamline the migration process. It can also unveil opportunities for a redesign of your site’s information and content architecture, aligned with a new content strategy. This process is not just about elimination, but also about discovery — uncovering what content is most valued by users. Not only are you finding out what you don’t need, but you’re hopefully finding out what's really important as well.

Given that moving from Drupal 7 to Drupal 10 essentially entails a complete site rebuild, there lies a golden opportunity to design the site around the content. This approach ensures that the site architecture complements and enhances the content, rather than forcing content to fit into a pre-existing structure.

The insights won here feed into the second crucial stage of a Drupal 7 migration: the design audit.

Design audit: An opportunity for enhancing UX

A design audit is where you and your Marketing team evaluate the current design’s effectiveness and explore the potential for a redesign. It goes hand-in-hand with a content audit.

Design audit objectives

  • Evaluate current design effectiveness: Before deciding on a redesign, critically assess how well your current design serves your content and users. Does it facilitate easy navigation? Is it aesthetically pleasing and functionally efficient?
  • Consider compatibility with Drupal 10: Drupal 10 brings new features and capabilities. The design audit of a Drupal 7 website usually reveals a rigid, template-based layout system, limiting content display versatility. By migrating to Drupal 10 and utilizing its advanced Layout Builder, the redesign can offer dynamic, user-friendly layouts, enhancing user engagement and providing flexibility for content strategy adaptations.

    Note that, while migrating away from Drupal 7, you essentially rebuild your site. Even if you choose to retain the existing design, adapting the look and feel to the newer Drupal version will require some level of reworking as well. If your existing design seems incompatible or would require extensive modifications, it might be more efficient to opt for a new design.

  • Align design with content strategy: The design should complement and enhance the content, not overshadow it. A design audit should involve close coordination with content strategists to ensure that the design facilitates the content’s purpose and enhances user engagement.
  • Explore modern design trends: Technology and design trends evolve rapidly. Use this migration as an opportunity to refresh your website’s look and feel to stay relevant and appealing to your audience.
  • Accessibility enhancement: Focus on improving the overall user experience for everyone. This includes optimizing the site for various devices and improving accessibility, for instance, compliance with A11Y guidelines.

Palantir not only offers technical expertise in migration processes but also provides skilled designers who can seamlessly collaborate with your team. Our designers are adept at working alongside content strategists. They ensure that you end up with a cohesive system that effectively supports and enhances your content strategy, ensuring that every aspect of your site’s design is driven by and aligned with your overall content goals.

Technical audit: Engineering a future-ready framework

Next up, your internal IT team should perform a comprehensive technical audit. If necessary, this stage can overlap with the content audits. However, we recommend that your migration should be primarily driven by the insights gained from your content audit.

The ultimate goal of the technical audit is preparing for a new Drupal environment. This means understanding how the identified technical elements will function in the new system and planning for any necessary adaptations or developments.

Data architecture audit

The technical audit begins with a detailed analysis of how data is structured in the current Drupal 7 site. This involves examining the entity relationships and the architecture of data storage. Understanding how different pieces of content are interlinked and how they reference each other is essential. This step not only overlaps with the content audit but also sets the stage for a smooth technical transition to Drupal 10.

Custom functionality and integration evaluation

A critical aspect of the technical audit is assessing any custom functionalities or third-party integrations present in the current system. This includes custom plug-in modules, single sign-on mechanisms, and other unique features. Each custom element that you migrate over is something you have to maintain, potentially throughout the lifetime of the site. The decision to migrate these elements should be based on their current value and necessity. During the audit, aim to determine which functionalities are essential and how they can be adapted or redeveloped for Drupal 10.

Driving collaborative decision-making

Collaboration between the IT/technical, marketing, content strategy, and design teams is vital in deciding what to keep (and migrate) and what to discard — regarding site content, architecture, code, and functionality. The technical audit, outlining the functionalities and integrations of the current site, guides the planning and decision-making process following the insights you gain from the content and design audits.

Conclusion: Charting the course of a Drupal migration

As we’ve seen, the journey away from Drupal 7 involves three main audits:

  • the content audit, which acts as a decluttering exercise;
  • the design audit, seizing opportunities to enhance user experience;
  • and the technical audit, engineering a future-ready framework. 

The content audit is the central pillar, and content strategy should drive the technical implementation and design decisions. This approach ensures a migration process where content seamlessly integrates into an efficient, updated site structure, rather than being confined by it.

Palantir is here to help and guide you through a successful migration from Drupal 7 to the future of your online digital presence. We are a Drupal Association–certified migration partner, with years of experience with intricate processes. Our expertise in content strategy, design innovation, and technical proficiency makes us an ideal full-service partner for navigating the complexities of a D7 end-of-life upgrade.

If you’re considering this critical step in your digital strategy, we invite you to explore how Palantir’s Continuous Delivery Portfolio (CDP) and web modernization services can transform your digital presence.

Jan 01 2024
Jan 01

On today’s show we share interviews we conducted with sponsors, speakers and attendees at New England Drupal Camp in November. Seventeen in all.

For show notes visit:
www.talkingDrupal.com/431

Topics

Interviews with:

Michael Miles
Nick Silverman
Matt O’Bryant
Ethan Aho
Mike Anello
Patrick Anderson
Brian Perry
Aubrey Sambor
Brigitte Ayerves Valderas
Chris Wells
Richard Hood
Chris Amato
Ivan Stegic
Philip Frilling
Rod Martin
Jacob Rockowitz
Whitney Hess

Hosts

Nic Laflin - nLighteneddevelopment.com nicxvan
John Picozzi - epam.com johnpicozzi
Martin Anderson-Clutz - mandclu
Stephen Cross - StephenCross.com

Dec 29 2023
Dec 29

Wir wünschen frohe Weihnachten und ein erfolgreiches neues Jahr!

Wir wünschen frohe Weihnachten, einen schönen Urlaub und einen guten Rutsch ins neue Jahr! Genießen Sie die Feiertage und starten Sie gut ins Jahr 2024!

drunomics Büro Wien22. Dezember 2023

Dec 28 2023
Dec 28

Authored by: Nadiia Nykolaichuk.

Online forms serve a myriad of purposes, including logging into accounts, leaving comments, and registering for events, to name a few. In an inclusive web environment, it is crucial that people with disabilities, who rely on assistive technology, can do these tasks seamlessly. That’s what makes accessible forms indispensable for overall website accessibility. 

So, after overviewing alt text and semantic HTML and WAI-ARIA in our previous posts, we are now turning our focus to accessible forms as the next topic in our series on essential accessibility elements. We will go through the nature of accessible forms, as well as some specific ways in which the Drupal CMS supports their accessibility.

Introduction to the world of accessible forms

Accessible forms are easy for users with disabilities to perceive, complete, and submit. The ease of working with forms shouldn’t be hampered by the fact that users rely on assistive tools like screen readers or screen magnifiers, use the keyboard only, or access your website with a touchscreen device, for example.

A journey through a form with a screen reader

A user navigates through an online form, often with the help of keyboard commands or gestures. A screen reader identifies the form elements such as fields, labels, headings, and associated instructions, all of which help it convey its structure. It translates the information into speech for the user or presents it as Braille. 

The screen reader indicates which form element is currently focused, helping the user understand where exactly they are in the form. It announces the type of each field (e.g., text input, checkbox, radio button), as well as provides information about the expected input. In case a submission error occurs, the screen reader announces the error message so the user can address the issue. 

The key characteristics of an accessible form

  • Semantic HTML. An accessible form uses semantic HTML elements such as , , , , and more to convey its structure. The form elements need to have a clear hierarchy. </li> <li><strong>WAI-ARIA.</strong> The accessibility of user interactions with the form is enhanced through the use of WAI-ARIA attributes if needed.</li> <li><strong>Descriptive labels.</strong> Each field has a clear and descriptive label that makes its purpose clear. Labels are explicitly associated with the corresponding form fields.</li> <li><strong>Focus and interaction indicators.</strong> An accessible form has clear indicators for the form elements that are currently in focus and for its current interaction state (e.g. ‘Checked’). This is crucial for users navigating with a keyboard or other non-mouse input methods. This includes both visually distinguishable styles for sighted users and announcements by screen readers for non-sighted users.</li> <li><strong>Clear instructions.</strong> There are clear instructions or hints that guide users through filling out the form fields. Required fields are explicitly indicated, users are able to hide or show help text, and so on.</li> <li><strong>Proper form validation and error handling.</strong> Properly structured, meaningful error messages for specific fields provide context and guidance on fixing the errors. Fields with errors are clearly highlighted.</li> <li><strong>Keyboard accessibility.</strong> Users can navigate and interact with the entire form relying solely on the keyboard. A logical tab order and a clear indication of the focus state on the form elements greatly helps that.</li> <li><strong>Contrast and readability.</strong> Sufficient color contrast for text and form controls helps users with low vision or color blindness. An accessible form also uses readable font sizes and styles, and avoids relying solely on color to convey the information.</li> <li><strong>Responsive design.</strong> Users with special needs have smooth experiences with form handling across various devices and screen sizes, including desktops, tablets, and smartphones.</li> <li><strong>Comfortable length.</strong> If a form is lengthy, it’s better to split into multiple pages or use headings for its sections.</li> </ul><h2>Accessible forms in Drupal</h2> <p>The Drupal CMS is renowned for prioritizing web accessibility and constantly embracing relevant best practices. It’s built to provide the best mobile experiences, uses HTML5 and WAI-ARIA, requires Alt text, and more. The Drupal community has done — and keeps doing — amazing work on increasing the accessibility of forms. The Drupal core themes, the Form API, the Inline Form Errors module, the Webform module, and other components and tools have introduced useful enhancements in this area. They make sure a wider range of people with disabilities can use accessible forms, and empower developers and site builders to create them.  </p> <h3>Form accessibility in Drupal’s new core themes</h3> <p>New Drupal default themes, Claro for the admin experiences and Olivero for the front end, are genuinely focused on accessibility in everything, including online forms. </p> <p>Here are some examples of how form accessibility is handled in the Olivero theme. The elements have a standard look that makes them more recognizable. A color bar on the left side is used to signal the form element. The labels are displayed above each field for perfect clarity in the process of completing a form.</p> <p>All the input elements, be it text fields, checkboxes, radio buttons, or submit buttons, are created with accessibility in mind and have properly implemented states (Focus, Checked, Disabled, Hover, and more) to let users know what is happening at every moment of their interaction with the form. Each state has a distinctive style (a specific color border around the element). </p> <figure role="group"><img alt="A demo of the states for the input elements in the Olivero theme." data-entity-type="file" data-entity-uuid="0ea1718c-66e6-42eb-a79b-395d575172d6" src="https://imagexmedia.com/sites/default/files/inline-images/states-for-inp..." width="1198" height="1999" loading="lazy" /><figcaption><em>A demo of the states for the input elements in the Olivero theme.</em></figcaption></figure><figure role="group"><img alt="Focus styles for the account creation form in the Olivero theme." data-entity-type="file" data-entity-uuid="2d8862cd-6c1f-4d48-b510-68f93688709e" src="https://imagexmedia.com/sites/default/files/inline-images/focus-styles-f..." width="1425" height="803" loading="lazy" /><figcaption><em>Focus styles for the account creation form in the Olivero theme.</em></figcaption></figure><p>The default admin theme Claro is yet another embodiment of accessibility. Among other features for accessible forms, it can boast form element sizes that <a href="https://www.drupal.org/docs/core-modules-and-themes/core-themes/claro-th...">meet</a> the Web Content Accessibility Guidelines (WCAG) at the AA level, which makes them much more convenient for the users of touch devices. The focus styles for the text input areas, checkboxes, and other elements are consistent and clear, drawing users attention to where they are in the form.</p> <figure role="group"><img alt="A demo of focus styles for all form elements in the Claro theme." data-entity-type="file" data-entity-uuid="8dcea8b6-52fc-4904-84cb-ddeef4cb3711" src="https://imagexmedia.com/sites/default/files/inline-images/focus-indicato..." width="1205" height="325" loading="lazy" /><figcaption><em>A demo of focus styles for all form elements in the Claro theme.</em></figcaption></figure><figure role="group"><img alt="Focus styles for the content editing form in the Claro theme." data-entity-type="file" data-entity-uuid="59b9ec68-cd9b-4ded-aff7-8c06a263e497" src="https://imagexmedia.com/sites/default/files/inline-images/focus-styles-f..." width="1509" height="768" loading="lazy" /><figcaption><em>Focus styles for the content editing form in the Claro theme.</em></figcaption></figure><h3>Form accessibility in the Drupal core Form API</h3> <p><a href="https://www.drupal.org/docs/drupal-apis/form-api">Form API (FAPI)</a> in the Drupal core is a developer-oriented set of tools for form creation and management. It is designed with accessibility in mind, supporting the best accessibility practices and handling a lot of the behind-the-scenes work to ensure compliance with accessibility standards. </p> <p>Here are at least some examples of important Form API features and related practices that enhance form accessibility:</p> <ul><li><strong>HTML5 support for form elements</strong></li> </ul><p>The Form API extensively supports modern semantic HTML5 elements that make forms more accessible by better conveying their structure to screen readers. For example, when Drupal 8.0 was released in 2015, the support for multiple new HTML5 elements <a href="https://www.drupal.org/node/1315186">were added to FAPI</a> such as the email, telephone, URL, search, number, color, and placeholder elements, as well as the <a href="https://www.drupal.org/node/1953036">‘#pattern’ property</a> to provide easy field validation. That was a great start to the enhancements of the form semantics that are still going on.</p> <ul><li><strong>Details VS. Fieldsets </strong></li> </ul><p>In Drupal 7 and earlier versions, the ‘FIELDSET’ and ‘LEGEND’ HTML elements were used to group related elements together into collapsible sets of fields. With the Drupal 8 release, new HTML5 ‘DETAILS’ and ‘SUMMARY’ elements <a href="https://www.drupal.org/node/1852020">were introduced</a> for collapsible fieldsets. The new elements provide a clear and meaningful structure to assistive technologies, have native support across modern browsers, are more consistent and straightforward in styling, provide built-in keyboard accessibility, and are well-suited for responsive web design.</p> <ul><li><strong>The #title property</strong></li> </ul><p>Drupal's Form API encourages the use of the #title property to provide clear and descriptive labels for form elements. This helps diverse audiences, including users relying on screen readers, understand the purpose of each element (e.g. ‘#title’ => t(‘Username’)).</p> <p>As part of ongoing accessibility enhancements, <a href="https://www.drupal.org/project/drupal/issues/933004">it’s planned to create a new core test in the upcoming Drupal 11</a> that will check every module's forms and ensure that the relevant form elements have a #title property. The forms that don’t have it won’t pass the test so they can’t be added to the Drupal core.</p> <ul><li><strong>The #type property</strong></li> </ul><p>The #type property is another property in Drupal’s Form API that’s essential for accessibility. It enables developers to specify the type of form element (e.g. ‘#type’ => ‘textfield’). This helps make forms more accessible by ensuring that screen readers can interpret and interact with specific form elements in a proper way.</p> <figure role="group"><img alt="An example of a code snippet with the #type and #title properties in a form." data-entity-type="file" data-entity-uuid="39022a15-80ba-4fc1-8b28-d08a0d28f643" src="https://imagexmedia.com/sites/default/files/inline-images/example-of-cod..." width="915" height="245" loading="lazy" /><figcaption><em>An example of a code snippet with the #type and #title properties in a form.</em></figcaption></figure><ul><li><strong>The #description property and the aria-describedby attribute</strong></li> </ul><p>The #description property is used in Drupal's Form API to provide additional information or context for form elements (e.g. ‘#description’ => t(‘Choose a unique username for your account.’). This text is often displayed below the element. </p> <p>However, for users who rely on assistive technologies like screen readers, the visual presentation may not be sufficient. This is where the ‘aria-describedby’ attribute comes into play. It is an Accessible Rich Internet Applications (ARIA) attribute that associates an HTML element with additional descriptive text. It makes sure that the screen reader reads out the additional description when the user interacts with the specific form element. The support for the attribute in the Form API was <a href="https://www.drupal.org/project/drupal/issues/405360">introduced</a> in Drupal 8.0, and it’s a great example of WAI-ARIA attributes used in the Form API. </p> <figure role="group"><img alt="An example of a code snippet with the aria-describedby attribute in a form." data-entity-type="file" data-entity-uuid="f5eb4af0-b266-49be-8107-1b6b972c9b9a" src="https://imagexmedia.com/sites/default/files/inline-images/example-of-cod..." width="922" height="162" loading="lazy" /><figcaption><em>An example of a code snippet with the aria-describedby attribute in a form.</em></figcaption></figure><ul><li><strong>Error handling</strong></li> </ul><p>Form API provides mechanisms for <a href="https://www.drupal.org/docs/drupal-apis/form-api/introduction-to-form-api">validating form submissions and handling errors</a>. It supports the display of form validation errors with clear error messages, helping users who rely on screen readers understand and address the errors. In Drupal 8 and later versions, the validateForm method is used for form validation.</p> <ul><li><strong>Ongoing FAPI improvements</strong></li> </ul><p>In addition to the above-mentioned and many other accessibility features and enhancements in FAPI, the work never stops in this area. A lot more is <a href="https://www.drupal.org/project/drupal/issues/2203649">planned to do</a> in the near future, mostly related to cementing in the semantic relationships in FAPI, checking for form field titles, ensuring the proper management of form labels, allowing customized validation messages, using ARIA to link descriptive text, and more.</p> <h3>Form accessibility in the Inline Form Errors core module</h3> <p>When it comes to the accessibility of form error messages, the Drupal core also includes a module called <a href="https://www.drupal.org/docs/8/core/modules/inline-form-errors/inline-for...">Inline Form Errors</a> (also known as IFE). The module’s mission is to make sure form validation errors are displayed inline with the specific fields that need attention. A semantic alert at the top of the page lists all the fields that have an error, providing links to specific fields that require correction.</p> <p>This provides more context and clarity for error handling. Users who rely on screen readers, screen magnifiers, and keyboard only are able to find the error for the relevant field with less effort.</p> <figure role="group"><img alt="Error handling by the Inline Form Errors module." data-entity-type="file" data-entity-uuid="aafce54c-815c-4fc1-96dc-c25e2144fe66" src="https://imagexmedia.com/sites/default/files/inline-images/error-handling..." width="499" height="278" loading="lazy" /><figcaption><em>Error handling by the Inline Form Errors module.</em></figcaption></figure><p>The Inline Form Errors module is not enabled by default in Drupal core. This is meant to keep the core installation minimal and allow site builders to make decisions based on their specific needs. The Drupal core itself provides methods for handling form errors, and developers have the flexibility to use modules like Inline Form Errors or implement custom solutions to create a more user-friendly error presentation.</p> <h3>Form accessibility in the Webform contributed module</h3> <p><a href="https://www.drupal.org/project/webform">Webform</a>, one of the most popular contributed modules of all times, provides an excellent UI with an outstanding choice of options for creating and handling online forms. The module’s creator, Jacob Rockowitz, wrote an article called “<a href="https://www.jrockowitz.com/blog/webform-diy-accessibility">Webform for Drupal 8: DIY Accessibility</a>” where he shared how he performed a DIY accessibility audit to properly self-assess the Webform module's accessibility as he was working on the stable module’s 8.x release.</p> <p>Among other things, Jacob Rockowitz mentioned that the combination of reviewing the WCAG guidelines and comparing the feedback from two automated tools, <a href="https://wave.webaim.org/">WAVE</a> and <a href="https://pa11y.org/">Pa11y</a>, helped him see some immediate issues. He found and fixed a few problems related to the proper labeling of form inputs with appropriate contextual information for screen readers. </p> <p>He also decided to publicly define the Webform module’s accessibility benchmark by adding the link to the audit results and the statement of commitment to accessibility to the project page. He encouraged the community to get involved with improving the accessibility of the Webform module and Drupal overall. He also created the <a href="https://www.drupal.org/docs/8/modules/webform/webform-accessibility">Webform Accessibility documentation page</a> on drupal.org.</p> <figure role="group"><img alt="Accessibility information on the Webform module’s page." data-entity-type="file" data-entity-uuid="6b15e79c-60e5-4bba-81c4-92269566b8f7" src="https://imagexmedia.com/sites/default/files/inline-images/accessibility-..." width="799" height="691" loading="lazy" /><figcaption><em>Accessibility information on the Webform module’s page.</em></figcaption></figure><h3>Final thoughts</h3> <p>There’s no doubt that Drupal will remain steadfast in its unwavering dedication to accessibility. The best practices and features adopted within the CMS for all website components, including online forms, create a super favorable environment for websites to be accessible to diverse audiences. It’s <a href="https://imagexmedia.com/higher-ed-website-accessibility-user-experience">more than just about meeting the accessibility standards</a> — it’s about a commitment to inclusivity and aligning with the progressive values of the modern world. With the help of a <a href="https://imagexmedia.com/about">trustworthy Drupal partner</a>, this mission is easy to fulfill.</p></div> </div> <div class="sibling-spacer py-5 col-xl-4 col-md-5"> <div class="learn-from-us bg-gray-100 pt-5 pb-5 ps-5 pe-5"> <div class="row"> <div class="mb-2 col-lg-12"><h6>Learn from us</h6> <div class="description">Sign up and receive our monthly insights directly in your inbox!</div> </div> <span id="webform-submission-learn-from-us-node-2705-form-ajax-content"></span><div id="webform-submission-learn-from-us-node-2705-form-ajax" class="webform-ajax-form-wrapper" data-effect="none" data-progress-type="throbber"> <form class="webform-submission-form webform-submission-add-form webform-submission-learn-from-us-form webform-submission-learn-from-us-add-form webform-submission-learn-from-us-node-2705-form webform-submission-learn-from-us-node-2705-add-form js-webform-details-toggle webform-details-toggle" data-drupal-selector="learnfromus" action="/blog/accessible-forms-in-drupal" method="post" id="learnFromUs" accept-charset="UTF-8"> <div class="learn-from-us-intro js-form-item form-item js-form-type-processed-text form-item- js-form-item- form-no-label" id="edit-processed-text"> <p>Subcribe to newsletter (no spam)</p> </div> <fieldset class="mb-3 js-webform-type-fieldset webform-type-fieldset js-form-item form-item js-form-wrapper form-wrapper" data-drupal-selector="edit-fields" id="edit-fields"> <legend> <span class="visually-hidden fieldset-legend">Fields</span> </legend> <div class="fieldset-wrapper"> <div class="form-item--two-column js-form-item form-item js-form-type-email form-item-email js-form-item-email"> <label for="edit-email" class="js-form-required form-required">Email</label> <input data-drupal-selector="edit-email" type="email" id="edit-email" name="email" value="" size="60" maxlength="254" class="form-email required" required="required" aria-required="true" /> </div> <div class="form-item--two-column dropdown-icon js-form-item form-item js-form-type-select form-item-country js-form-item-country"> <label for="edit-country" class="js-form-required form-required">Country</label> <select data-drupal-selector="edit-country" id="edit-country" name="country" class="form-select required" required="required" aria-required="true"><option value=" "></option><option value="Afghanistan">Afghanistan</option><option value="Albania">Albania</option><option value="Algeria">Algeria</option><option value="American Samoa">American Samoa</option><option value="Andorra">Andorra</option><option value="Angola">Angola</option><option value="Anguilla">Anguilla</option><option value="Antarctica">Antarctica</option><option value="Antigua & Barbuda">Antigua & Barbuda</option><option value="Argentina">Argentina</option><option value="Armenia">Armenia</option><option value="Aruba">Aruba</option><option value="Ascension Island">Ascension Island</option><option value="Australia">Australia</option><option value="Austria">Austria</option><option value="Azerbaijan">Azerbaijan</option><option value="Bahamas">Bahamas</option><option value="Bahrain">Bahrain</option><option value="Bangladesh">Bangladesh</option><option value="Barbados">Barbados</option><option value="Belarus">Belarus</option><option value="Belgium">Belgium</option><option value="Belize">Belize</option><option value="Benin">Benin</option><option value="Bermuda">Bermuda</option><option value="Bhutan">Bhutan</option><option value="Bolivia">Bolivia</option><option value="Bosnia & Herzegovina">Bosnia & Herzegovina</option><option value="Botswana">Botswana</option><option value="Bouvet Island">Bouvet Island</option><option value="Brazil">Brazil</option><option value="British Indian Ocean Territory">British Indian Ocean Territory</option><option value="British Virgin Islands">British Virgin Islands</option><option value="Brunei">Brunei</option><option value="Bulgaria">Bulgaria</option><option value="Burkina Faso">Burkina Faso</option><option value="Burundi">Burundi</option><option value="Cambodia">Cambodia</option><option value="Cameroon">Cameroon</option><option value="Canada">Canada</option><option value="Canary Islands">Canary Islands</option><option value="Cape Verde">Cape Verde</option><option value="Caribbean Netherlands">Caribbean Netherlands</option><option value="Cayman Islands">Cayman Islands</option><option value="Central African Republic">Central African Republic</option><option value="Ceuta & Melilla">Ceuta & Melilla</option><option value="Chad">Chad</option><option value="Chile">Chile</option><option value="China">China</option><option value="Christmas Island">Christmas Island</option><option value="Clipperton Island">Clipperton Island</option><option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option><option value="Colombia">Colombia</option><option value="Comoros">Comoros</option><option value="Congo - Brazzaville">Congo - Brazzaville</option><option value="Congo - Kinshasa">Congo - Kinshasa</option><option value="Cook Islands">Cook Islands</option><option value="Costa Rica">Costa Rica</option><option value="Croatia">Croatia</option><option value="Cuba">Cuba</option><option value="Curaçao">Curaçao</option><option value="Cyprus">Cyprus</option><option value="Czechia">Czechia</option><option value="Côte d’Ivoire">Côte d’Ivoire</option><option value="Denmark">Denmark</option><option value="Diego Garcia">Diego Garcia</option><option value="Djibouti">Djibouti</option><option value="Dominica">Dominica</option><option value="Dominican Republic">Dominican Republic</option><option value="Ecuador">Ecuador</option><option value="Egypt">Egypt</option><option value="El Salvador">El Salvador</option><option value="Equatorial Guinea">Equatorial Guinea</option><option value="Eritrea">Eritrea</option><option value="Estonia">Estonia</option><option value="Eswatini">Eswatini</option><option value="Ethiopia">Ethiopia</option><option value="Falkland Islands">Falkland Islands</option><option value="Faroe Islands">Faroe Islands</option><option value="Fiji">Fiji</option><option value="Finland">Finland</option><option value="France">France</option><option value="French Guiana">French Guiana</option><option value="French Polynesia">French Polynesia</option><option value="French Southern Territories">French Southern Territories</option><option value="Gabon">Gabon</option><option value="Gambia">Gambia</option><option value="Georgia">Georgia</option><option value="Germany">Germany</option><option value="Ghana">Ghana</option><option value="Gibraltar">Gibraltar</option><option value="Greece">Greece</option><option value="Greenland">Greenland</option><option value="Grenada">Grenada</option><option value="Guadeloupe">Guadeloupe</option><option value="Guam">Guam</option><option value="Guatemala">Guatemala</option><option value="Guernsey">Guernsey</option><option value="Guinea">Guinea</option><option value="Guinea-Bissau">Guinea-Bissau</option><option value="Guyana">Guyana</option><option value="Haiti">Haiti</option><option value="Heard & McDonald Islands">Heard & McDonald Islands</option><option value="Honduras">Honduras</option><option value="Hong Kong SAR China">Hong Kong SAR China</option><option value="Hungary">Hungary</option><option value="Iceland">Iceland</option><option value="India">India</option><option value="Indonesia">Indonesia</option><option value="Iran">Iran</option><option value="Iraq">Iraq</option><option value="Ireland">Ireland</option><option value="Isle of Man">Isle of Man</option><option value="Israel">Israel</option><option value="Italy">Italy</option><option value="Jamaica">Jamaica</option><option value="Japan">Japan</option><option value="Jersey">Jersey</option><option value="Jordan">Jordan</option><option value="Kazakhstan">Kazakhstan</option><option value="Kenya">Kenya</option><option value="Kiribati">Kiribati</option><option value="Kosovo">Kosovo</option><option value="Kuwait">Kuwait</option><option value="Kyrgyzstan">Kyrgyzstan</option><option value="Laos">Laos</option><option value="Latvia">Latvia</option><option value="Lebanon">Lebanon</option><option value="Lesotho">Lesotho</option><option value="Liberia">Liberia</option><option value="Libya">Libya</option><option value="Liechtenstein">Liechtenstein</option><option value="Lithuania">Lithuania</option><option value="Luxembourg">Luxembourg</option><option value="Macao SAR China">Macao SAR China</option><option value="Madagascar">Madagascar</option><option value="Malawi">Malawi</option><option value="Malaysia">Malaysia</option><option value="Maldives">Maldives</option><option value="Mali">Mali</option><option value="Malta">Malta</option><option value="Marshall Islands">Marshall Islands</option><option value="Martinique">Martinique</option><option value="Mauritania">Mauritania</option><option value="Mauritius">Mauritius</option><option value="Mayotte">Mayotte</option><option value="Mexico">Mexico</option><option value="Micronesia">Micronesia</option><option value="Moldova">Moldova</option><option value="Monaco">Monaco</option><option value="Mongolia">Mongolia</option><option value="Montenegro">Montenegro</option><option value="Montserrat">Montserrat</option><option value="Morocco">Morocco</option><option value="Mozambique">Mozambique</option><option value="Myanmar (Burma)">Myanmar (Burma)</option><option value="Namibia">Namibia</option><option value="Nauru">Nauru</option><option value="Nepal">Nepal</option><option value="Netherlands">Netherlands</option><option value="Netherlands Antilles">Netherlands Antilles</option><option value="New Caledonia">New Caledonia</option><option value="New Zealand">New Zealand</option><option value="Nicaragua">Nicaragua</option><option value="Niger">Niger</option><option value="Nigeria">Nigeria</option><option value="Niue">Niue</option><option value="Norfolk Island">Norfolk Island</option><option value="Northern Mariana Islands">Northern Mariana Islands</option><option value="North Korea">North Korea</option><option value="North Macedonia">North Macedonia</option><option value="Norway">Norway</option><option value="Oman">Oman</option><option value="Outlying Oceania">Outlying Oceania</option><option value="Pakistan">Pakistan</option><option value="Palau">Palau</option><option value="Palestinian Territories">Palestinian Territories</option><option value="Panama">Panama</option><option value="Papua New Guinea">Papua New Guinea</option><option value="Paraguay">Paraguay</option><option value="Peru">Peru</option><option value="Philippines">Philippines</option><option value="Pitcairn Islands">Pitcairn Islands</option><option value="Poland">Poland</option><option value="Portugal">Portugal</option><option value="Puerto Rico">Puerto Rico</option><option value="Qatar">Qatar</option><option value="Romania">Romania</option><option value="Russia">Russia</option><option value="Rwanda">Rwanda</option><option value="Réunion">Réunion</option><option value="Samoa">Samoa</option><option value="San Marino">San Marino</option><option value="Saudi Arabia">Saudi Arabia</option><option value="Senegal">Senegal</option><option value="Serbia">Serbia</option><option value="Seychelles">Seychelles</option><option value="Sierra Leone">Sierra Leone</option><option value="Singapore">Singapore</option><option value="Sint Maarten">Sint Maarten</option><option value="Slovakia">Slovakia</option><option value="Slovenia">Slovenia</option><option value="Solomon Islands">Solomon Islands</option><option value="Somalia">Somalia</option><option value="South Africa">South Africa</option><option value="South Georgia & South Sandwich Islands">South Georgia & South Sandwich Islands</option><option value="South Korea">South Korea</option><option value="South Sudan">South Sudan</option><option value="Spain">Spain</option><option value="Sri Lanka">Sri Lanka</option><option value="St. Barthélemy">St. Barthélemy</option><option value="St. Helena">St. Helena</option><option value="St. Kitts & Nevis">St. Kitts & Nevis</option><option value="St. Lucia">St. Lucia</option><option value="St. Martin">St. Martin</option><option value="St. Pierre & Miquelon">St. Pierre & Miquelon</option><option value="St. Vincent & Grenadines">St. Vincent & Grenadines</option><option value="Sudan">Sudan</option><option value="Suriname">Suriname</option><option value="Svalbard & Jan Mayen">Svalbard & Jan Mayen</option><option value="Sweden">Sweden</option><option value="Switzerland">Switzerland</option><option value="Syria">Syria</option><option value="São Tomé & Príncipe">São Tomé & Príncipe</option><option value="Taiwan">Taiwan</option><option value="Tajikistan">Tajikistan</option><option value="Tanzania">Tanzania</option><option value="Thailand">Thailand</option><option value="Timor-Leste">Timor-Leste</option><option value="Togo">Togo</option><option value="Tokelau">Tokelau</option><option value="Tonga">Tonga</option><option value="Trinidad & Tobago">Trinidad & Tobago</option><option value="Tristan da Cunha">Tristan da Cunha</option><option value="Tunisia">Tunisia</option><option value="Turkey">Turkey</option><option value="Turkmenistan">Turkmenistan</option><option value="Turks & Caicos Islands">Turks & Caicos Islands</option><option value="Tuvalu">Tuvalu</option><option value="U.S. Outlying Islands">U.S. Outlying Islands</option><option value="U.S. Virgin Islands">U.S. Virgin Islands</option><option value="Uganda">Uganda</option><option value="Ukraine">Ukraine</option><option value="United Arab Emirates">United Arab Emirates</option><option value="United Kingdom">United Kingdom</option><option value="United States">United States</option><option value="Uruguay">Uruguay</option><option value="Uzbekistan">Uzbekistan</option><option value="Vanuatu">Vanuatu</option><option value="Vatican City">Vatican City</option><option value="Venezuela">Venezuela</option><option value="Vietnam">Vietnam</option><option value="Wallis & Futuna">Wallis & Futuna</option><option value="Western Sahara">Western Sahara</option><option value="Yemen">Yemen</option><option value="Zambia">Zambia</option><option value="Zimbabwe">Zimbabwe</option><option value="Åland Islands">Åland Islands</option></select> </div> <div class="form-item--two-column js-form-item js-form-type-checkbox form-type-checkbox js-form-item-opt-in form-item-opt-in form-check"> <input class="form-check-input form-checkbox required" data-webform-required-error="You need to agree to receive updates and information." data-drupal-selector="edit-opt-in" type="checkbox" id="edit-opt-in" name="opt_in" value="1" required="required" aria-required="true" /> <label class="form-check-label option js-form-required form-required" for="edit-opt-in">By checking this box, I agree to receive updates and information about ImageX. I understand that I can unsubscribe at any time.</label> <span></span> </div> </div> </fieldset> <div data-drupal-selector="edit-captcha" class="captcha captcha-type-challenge--learn-from-us"> <div class="captcha__element"> <input data-drupal-selector="edit-captcha-sid" type="hidden" name="captcha_sid" value="9179202" /> <input data-drupal-selector="edit-captcha-token" type="hidden" name="captcha_token" value="TLen9YpLF8YrqI7N6zz_X8lns1Ebs0svrFuLk3jNsFA" /> <input id="recaptcha-v3-token" class="recaptcha-v3-token" data-recaptcha-v3-action="learn_from_us" data-recaptcha-v3-site-key="6LeN-0khAAAAAMTraqYaGpVfrIZpdkJ29ps9gCHp" data-drupal-selector="edit-captcha-response" type="hidden" name="captcha_response" value="" /> <input data-drupal-selector="edit-is-recaptcha-v3" type="hidden" name="is_recaptcha_v3" value="1" /> </div> </div> <div class="form-actions--align-left mt-auto mb-lg-4 mb-3 form-actions webform-actions js-form-wrapper form-wrapper" data-drupal-selector="edit-actions" id="edit-actions"> <input class="webform-button--submit button button--primary js-form-submit form-submit btn btn-outline-blue-medium" data-drupal-selector="edit-actions-submit" data-disable-refocus="true" type="submit" id="edit-actions-submit" name="op" value="Submit" /> </div> <input autocomplete="off" data-drupal-selector="form-eghlo1hokmv2-5ghut37yl9cokgfpycykljhsmqxhtu" type="hidden" name="form_build_id" value="form-eghLO1hokmv2_5ghUt37yl9cOKgFPYcYKLJhsMqxhTU" /> <input data-drupal-selector="edit-webform-submission-learn-from-us-node-2705-add-form" type="hidden" name="form_id" value="webform_submission_learn_from_us_node_2705_add_form" /> </form> </div> </div> </div> <div class="bg-gray-100 p-4 pb-lg-3 px-lg-5 pt-lg-5 related-service-blog-posts"> <div class="views-element-container"><div class="js-view-dom-id-c2aa99d3b322066ed5a3bd721e7496468a9ac31e4d907efadb036e6f8bb8dbc8"> <div class="ixm__vertical-card mb-3 views-row"> <a href="https://imagexmedia.com/blog/3-questions-consider-before-multi-site-web-..." role="article" about="/blog/3-questions-consider-before-multi-site-web-project" class="node node--type-blog node--view-mode-blog-list-vertical ixm__teaser-item text-gray-800 ixm__teaser-item--vertical card mx-auto"> <div class="align-items-center row no-gutters"> <div class="row"> <div class="col-auto col"> <div class="field field--name-field-blog-banner-image field--type-image field--label-hidden field__item"> <img loading="lazy" src="https://imagexmedia.com/sites/default/files/styles/crop_blog_sidebar_car..." width="269" height="140" alt="Matt and Mahya working on a computer" typeof="foaf:Image" /> </div> </div> </div> <div class="row"> <div class="col"> <div class="p-4"> <div class="font-size-sm text-gray-500 font-heading">Mar 14 2024</div> <h5 class="transition__ease-link mt-2"><span>3 Questions to Consider Before You Begin a Multi-Site Web Development Project</span> </h5> </div> </div> </div> </div> </a> </div> <div class="ixm__vertical-card mb-3 views-row"> <a href="https://imagexmedia.com/blog/canadiansme-nomination-2023" role="article" about="/blog/canadiansme-nomination-2023" class="node node--type-blog node--view-mode-blog-list-vertical ixm__teaser-item text-gray-800 ixm__teaser-item--vertical card mx-auto"> <div class="align-items-center row no-gutters"> <div class="row"> <div class="col-auto col"> <div class="field field--name-field-blog-banner-image field--type-image field--label-hidden field__item"> <img loading="lazy" src="https://imagexmedia.com/sites/default/files/styles/crop_blog_sidebar_car..." width="269" height="140" alt="Gold confetti" typeof="foaf:Image" /> </div> </div> </div> <div class="row"> <div class="col"> <div class="p-4"> <div class="font-size-sm text-gray-500 font-heading">Mar 12 2024</div> <h5 class="transition__ease-link mt-2"><span>ImageX Nominated for CanadianSME National Business Awards 2023</span> </h5> </div> </div> </div> </div> </a> </div> <div class="ixm__vertical-card mb-3 views-row"> <a href="https://imagexmedia.com/blog/integrate-zoom-meetings-drupal" role="article" about="/blog/integrate-zoom-meetings-drupal" class="node node--type-blog node--view-mode-blog-list-vertical ixm__teaser-item text-gray-800 ixm__teaser-item--vertical card mx-auto"> <div class="align-items-center row no-gutters"> <div class="row"> <div class="col-auto col"> <div class="field field--name-field-blog-banner-image field--type-image field--label-hidden field__item"> <img loading="lazy" src="https://imagexmedia.com/sites/default/files/styles/crop_blog_sidebar_car..." width="269" height="140" alt="video call" typeof="foaf:Image" /> </div> </div> </div> <div class="row"> <div class="col"> <div class="p-4"> <div class="font-size-sm text-gray-500 font-heading">Mar 11 2024</div> <h5 class="transition__ease-link mt-2"><span>Integrate Zoom Meetings Seamlessly into Your Drupal Website via Our Developer’s Module</span> </h5> </div> </div> </div> </div> </a> </div> </div> </div> </div> <div class="bg-gray-100 p-4 p-lg-5"> <h5 class="fw-bold">You might also like</h5> <div class="views-element-container"><div class="js-view-dom-id-6160ba34a77fedcd850f3b2fd61167346478d13004c01444cd7a63f9a59fab74"> <div class="views-row"> <div role="article" about="/blog/3-questions-consider-before-multi-site-web-project" class="node node--type-blog node--view-mode-blog-list-card card card-mini mt-3"> <a href="https://imagexmedia.com/blog/3-questions-consider-before-multi-site-web-..." class="d-block py-4 px-3 text-purple stretched-link card-link"><span>3 Questions to Consider Before You Begin a Multi-Site Web Development Project</span> </a> </div> </div> <div class="views-row"> <div role="article" about="/blog/5-steps-exceptional-website-ux" class="node node--type-blog node--view-mode-blog-list-card card card-mini mt-3"> <a href="https://imagexmedia.com/blog/5-steps-exceptional-website-ux" class="d-block py-4 px-3 text-purple stretched-link card-link"><span>User Testing 101: 5 Tips for Including UX testing in Your Web Project</span> </a> </div> </div> <div class="views-row"> <div role="article" about="/blog/drupal-new-field-ui" class="node node--type-blog node--view-mode-blog-list-card card card-mini mt-3"> <a href="https://imagexmedia.com/blog/drupal-new-field-ui" class="d-block py-4 px-3 text-purple stretched-link card-link"><span>Mastering Content Structure with Ease Thanks to Drupal’s Revamped Field UI</span> </a> </div> </div> <div class="views-row"> <div role="article" about="/blog/captions-subtitles-transcripts-accessible-multimedia-drupal" class="node node--type-blog node--view-mode-blog-list-card card card-mini mt-3"> <a href="https://imagexmedia.com/blog/captions-subtitles-transcripts-accessible-m..." class="d-block py-4 px-3 text-purple stretched-link card-link"><span>Accessibility Elements, Part 5: Captions, Subtitles, Transcripts, and Audio Descriptions in Drupal</span> </a> </div> </div> </div> </div> </div> </div> </div> </div> </div> </article> </div> </div> </main> <footer class="main-footer text-bg-blue-light py-5 pt-lg-9 pb-lg-5"> <div class="container-xxl"> <div class="row"> <div class="col-12"> <h3 class="display-2 fw-bolder d-inline"> <a data-hover="Let's Chat" href="https://imagexmedia.com/contact" class="link-lets-chat d-inline-flex align-items-center gap-5 text-reset"> Let's Chat <i class="icon icon-arrow-right-long icon-6x d-inline-block" role="presentation"> <svg role="img" xmlns:xlink="http://www.w3.org/1999/xlink" fill="currentColor" viewBox="0 0 20 20"> <use xlink:href="#arrow-right-long"></use> </svg> </i> </a> </h3> <div class="py-5 py-lg-7 my-5 mb-lg-6 border-bottom border-top border-light"> <div class="row"> <div class="col-12 col-lg-6 col-xl-5 mb-6 mb-lg-0"> <span id="webform-submission-learn-from-us-node-2705-form-ajax-content"></span><div id="webform-submission-learn-from-us-node-2705-form-ajax" class="webform-ajax-form-wrapper" data-effect="none" data-progress-type="throbber"> <form class="webform-submission-form webform-submission-add-form webform-submission-learn-from-us-form webform-submission-learn-from-us-add-form webform-submission-learn-from-us-node-2705-form webform-submission-learn-from-us-node-2705-add-form js-webform-details-toggle webform-details-toggle" data-drupal-selector="learnfromus" action="/blog/accessible-forms-in-drupal" method="post" id="learnFromUs" accept-charset="UTF-8"> <div class="learn-from-us-intro js-form-item form-item js-form-type-processed-text form-item- js-form-item- form-no-label" id="edit-processed-text--2"> <p>Subcribe to newsletter (no spam)</p> </div> <fieldset class="mb-3 js-webform-type-fieldset webform-type-fieldset js-form-item form-item js-form-wrapper form-wrapper" data-drupal-selector="edit-fields" id="edit-fields--2"> <legend> <span class="visually-hidden fieldset-legend">Fields</span> </legend> <div class="fieldset-wrapper"> <div class="form-item--two-column js-form-item form-item js-form-type-email form-item-email js-form-item-email"> <label for="edit-email--2" class="js-form-required form-required">Email</label> <input data-drupal-selector="edit-email" type="email" id="edit-email--2" name="email" value="" size="60" maxlength="254" class="form-email required" required="required" aria-required="true" /> </div> <div class="form-item--two-column dropdown-icon js-form-item form-item js-form-type-select form-item-country js-form-item-country"> <label for="edit-country--2" class="js-form-required form-required">Country</label> <select data-drupal-selector="edit-country" id="edit-country--2" name="country" class="form-select required" required="required" aria-required="true"><option value=" "></option><option value="Afghanistan">Afghanistan</option><option value="Albania">Albania</option><option value="Algeria">Algeria</option><option value="American Samoa">American Samoa</option><option value="Andorra">Andorra</option><option value="Angola">Angola</option><option value="Anguilla">Anguilla</option><option value="Antarctica">Antarctica</option><option value="Antigua & Barbuda">Antigua & Barbuda</option><option value="Argentina">Argentina</option><option value="Armenia">Armenia</option><option value="Aruba">Aruba</option><option value="Ascension Island">Ascension Island</option><option value="Australia">Australia</option><option value="Austria">Austria</option><option value="Azerbaijan">Azerbaijan</option><option value="Bahamas">Bahamas</option><option value="Bahrain">Bahrain</option><option value="Bangladesh">Bangladesh</option><option value="Barbados">Barbados</option><option value="Belarus">Belarus</option><option value="Belgium">Belgium</option><option value="Belize">Belize</option><option value="Benin">Benin</option><option value="Bermuda">Bermuda</option><option value="Bhutan">Bhutan</option><option value="Bolivia">Bolivia</option><option value="Bosnia & Herzegovina">Bosnia & Herzegovina</option><option value="Botswana">Botswana</option><option value="Bouvet Island">Bouvet Island</option><option value="Brazil">Brazil</option><option value="British Indian Ocean Territory">British Indian Ocean Territory</option><option value="British Virgin Islands">British Virgin Islands</option><option value="Brunei">Brunei</option><option value="Bulgaria">Bulgaria</option><option value="Burkina Faso">Burkina Faso</option><option value="Burundi">Burundi</option><option value="Cambodia">Cambodia</option><option value="Cameroon">Cameroon</option><option value="Canada">Canada</option><option value="Canary Islands">Canary Islands</option><option value="Cape Verde">Cape Verde</option><option value="Caribbean Netherlands">Caribbean Netherlands</option><option value="Cayman Islands">Cayman Islands</option><option value="Central African Republic">Central African Republic</option><option value="Ceuta & Melilla">Ceuta & Melilla</option><option value="Chad">Chad</option><option value="Chile">Chile</option><option value="China">China</option><option value="Christmas Island">Christmas Island</option><option value="Clipperton Island">Clipperton Island</option><option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option><option value="Colombia">Colombia</option><option value="Comoros">Comoros</option><option value="Congo - Brazzaville">Congo - Brazzaville</option><option value="Congo - Kinshasa">Congo - Kinshasa</option><option value="Cook Islands">Cook Islands</option><option value="Costa Rica">Costa Rica</option><option value="Croatia">Croatia</option><option value="Cuba">Cuba</option><option value="Curaçao">Curaçao</option><option value="Cyprus">Cyprus</option><option value="Czechia">Czechia</option><option value="Côte d’Ivoire">Côte d’Ivoire</option><option value="Denmark">Denmark</option><option value="Diego Garcia">Diego Garcia</option><option value="Djibouti">Djibouti</option><option value="Dominica">Dominica</option><option value="Dominican Republic">Dominican Republic</option><option value="Ecuador">Ecuador</option><option value="Egypt">Egypt</option><option value="El Salvador">El Salvador</option><option value="Equatorial Guinea">Equatorial Guinea</option><option value="Eritrea">Eritrea</option><option value="Estonia">Estonia</option><option value="Eswatini">Eswatini</option><option value="Ethiopia">Ethiopia</option><option value="Falkland Islands">Falkland Islands</option><option value="Faroe Islands">Faroe Islands</option><option value="Fiji">Fiji</option><option value="Finland">Finland</option><option value="France">France</option><option value="French Guiana">French Guiana</option><option value="French Polynesia">French Polynesia</option><option value="French Southern Territories">French Southern Territories</option><option value="Gabon">Gabon</option><option value="Gambia">Gambia</option><option value="Georgia">Georgia</option><option value="Germany">Germany</option><option value="Ghana">Ghana</option><option value="Gibraltar">Gibraltar</option><option value="Greece">Greece</option><option value="Greenland">Greenland</option><option value="Grenada">Grenada</option><option value="Guadeloupe">Guadeloupe</option><option value="Guam">Guam</option><option value="Guatemala">Guatemala</option><option value="Guernsey">Guernsey</option><option value="Guinea">Guinea</option><option value="Guinea-Bissau">Guinea-Bissau</option><option value="Guyana">Guyana</option><option value="Haiti">Haiti</option><option value="Heard & McDonald Islands">Heard & McDonald Islands</option><option value="Honduras">Honduras</option><option value="Hong Kong SAR China">Hong Kong SAR China</option><option value="Hungary">Hungary</option><option value="Iceland">Iceland</option><option value="India">India</option><option value="Indonesia">Indonesia</option><option value="Iran">Iran</option><option value="Iraq">Iraq</option><option value="Ireland">Ireland</option><option value="Isle of Man">Isle of Man</option><option value="Israel">Israel</option><option value="Italy">Italy</option><option value="Jamaica">Jamaica</option><option value="Japan">Japan</option><option value="Jersey">Jersey</option><option value="Jordan">Jordan</option><option value="Kazakhstan">Kazakhstan</option><option value="Kenya">Kenya</option><option value="Kiribati">Kiribati</option><option value="Kosovo">Kosovo</option><option value="Kuwait">Kuwait</option><option value="Kyrgyzstan">Kyrgyzstan</option><option value="Laos">Laos</option><option value="Latvia">Latvia</option><option value="Lebanon">Lebanon</option><option value="Lesotho">Lesotho</option><option value="Liberia">Liberia</option><option value="Libya">Libya</option><option value="Liechtenstein">Liechtenstein</option><option value="Lithuania">Lithuania</option><option value="Luxembourg">Luxembourg</option><option value="Macao SAR China">Macao SAR China</option><option value="Madagascar">Madagascar</option><option value="Malawi">Malawi</option><option value="Malaysia">Malaysia</option><option value="Maldives">Maldives</option><option value="Mali">Mali</option><option value="Malta">Malta</option><option value="Marshall Islands">Marshall Islands</option><option value="Martinique">Martinique</option><option value="Mauritania">Mauritania</option><option value="Mauritius">Mauritius</option><option value="Mayotte">Mayotte</option><option value="Mexico">Mexico</option><option value="Micronesia">Micronesia</option><option value="Moldova">Moldova</option><option value="Monaco">Monaco</option><option value="Mongolia">Mongolia</option><option value="Montenegro">Montenegro</option><option value="Montserrat">Montserrat</option><option value="Morocco">Morocco</option><option value="Mozambique">Mozambique</option><option value="Myanmar (Burma)">Myanmar (Burma)</option><option value="Namibia">Namibia</option><option value="Nauru">Nauru</option><option value="Nepal">Nepal</option><option value="Netherlands">Netherlands</option><option value="Netherlands Antilles">Netherlands Antilles</option><option value="New Caledonia">New Caledonia</option><option value="New Zealand">New Zealand</option><option value="Nicaragua">Nicaragua</option><option value="Niger">Niger</option><option value="Nigeria">Nigeria</option><option value="Niue">Niue</option><option value="Norfolk Island">Norfolk Island</option><option value="Northern Mariana Islands">Northern Mariana Islands</option><option value="North Korea">North Korea</option><option value="North Macedonia">North Macedonia</option><option value="Norway">Norway</option><option value="Oman">Oman</option><option value="Outlying Oceania">Outlying Oceania</option><option value="Pakistan">Pakistan</option><option value="Palau">Palau</option><option value="Palestinian Territories">Palestinian Territories</option><option value="Panama">Panama</option><option value="Papua New Guinea">Papua New Guinea</option><option value="Paraguay">Paraguay</option><option value="Peru">Peru</option><option value="Philippines">Philippines</option><option value="Pitcairn Islands">Pitcairn Islands</option><option value="Poland">Poland</option><option value="Portugal">Portugal</option><option value="Puerto Rico">Puerto Rico</option><option value="Qatar">Qatar</option><option value="Romania">Romania</option><option value="Russia">Russia</option><option value="Rwanda">Rwanda</option><option value="Réunion">Réunion</option><option value="Samoa">Samoa</option><option value="San Marino">San Marino</option><option value="Saudi Arabia">Saudi Arabia</option><option value="Senegal">Senegal</option><option value="Serbia">Serbia</option><option value="Seychelles">Seychelles</option><option value="Sierra Leone">Sierra Leone</option><option value="Singapore">Singapore</option><option value="Sint Maarten">Sint Maarten</option><option value="Slovakia">Slovakia</option><option value="Slovenia">Slovenia</option><option value="Solomon Islands">Solomon Islands</option><option value="Somalia">Somalia</option><option value="South Africa">South Africa</option><option value="South Georgia & South Sandwich Islands">South Georgia & South Sandwich Islands</option><option value="South Korea">South Korea</option><option value="South Sudan">South Sudan</option><option value="Spain">Spain</option><option value="Sri Lanka">Sri Lanka</option><option value="St. Barthélemy">St. Barthélemy</option><option value="St. Helena">St. Helena</option><option value="St. Kitts & Nevis">St. Kitts & Nevis</option><option value="St. Lucia">St. Lucia</option><option value="St. Martin">St. Martin</option><option value="St. Pierre & Miquelon">St. Pierre & Miquelon</option><option value="St. Vincent & Grenadines">St. Vincent & Grenadines</option><option value="Sudan">Sudan</option><option value="Suriname">Suriname</option><option value="Svalbard & Jan Mayen">Svalbard & Jan Mayen</option><option value="Sweden">Sweden</option><option value="Switzerland">Switzerland</option><option value="Syria">Syria</option><option value="São Tomé & Príncipe">São Tomé & Príncipe</option><option value="Taiwan">Taiwan</option><option value="Tajikistan">Tajikistan</option><option value="Tanzania">Tanzania</option><option value="Thailand">Thailand</option><option value="Timor-Leste">Timor-Leste</option><option value="Togo">Togo</option><option value="Tokelau">Tokelau</option><option value="Tonga">Tonga</option><option value="Trinidad & Tobago">Trinidad & Tobago</option><option value="Tristan da Cunha">Tristan da Cunha</option><option value="Tunisia">Tunisia</option><option value="Turkey">Turkey</option><option value="Turkmenistan">Turkmenistan</option><option value="Turks & Caicos Islands">Turks & Caicos Islands</option><option value="Tuvalu">Tuvalu</option><option value="U.S. Outlying Islands">U.S. Outlying Islands</option><option value="U.S. Virgin Islands">U.S. Virgin Islands</option><option value="Uganda">Uganda</option><option value="Ukraine">Ukraine</option><option value="United Arab Emirates">United Arab Emirates</option><option value="United Kingdom">United Kingdom</option><option value="United States">United States</option><option value="Uruguay">Uruguay</option><option value="Uzbekistan">Uzbekistan</option><option value="Vanuatu">Vanuatu</option><option value="Vatican City">Vatican City</option><option value="Venezuela">Venezuela</option><option value="Vietnam">Vietnam</option><option value="Wallis & Futuna">Wallis & Futuna</option><option value="Western Sahara">Western Sahara</option><option value="Yemen">Yemen</option><option value="Zambia">Zambia</option><option value="Zimbabwe">Zimbabwe</option><option value="Åland Islands">Åland Islands</option></select> </div> <div class="form-item--two-column js-form-item js-form-type-checkbox form-type-checkbox js-form-item-opt-in form-item-opt-in form-check"> <input class="form-check-input form-checkbox required" data-webform-required-error="You need to agree to receive updates and information." data-drupal-selector="edit-opt-in" type="checkbox" id="edit-opt-in--2" name="opt_in" value="1" required="required" aria-required="true" /> <label class="form-check-label option js-form-required form-required" for="edit-opt-in--2">By checking this box, I agree to receive updates and information about ImageX. I understand that I can unsubscribe at any time.</label> <span></span> </div> </div> </fieldset> <div data-drupal-selector="edit-captcha" class="captcha captcha-type-challenge--learn-from-us"> <div class="captcha__element"> <input data-drupal-selector="edit-captcha-sid" type="hidden" name="captcha_sid" value="9179204" /> <input data-drupal-selector="edit-captcha-token" type="hidden" name="captcha_token" value="esIHMSNKa1S2DVnHK8X2KVme-yMIlJ2cJjc9bhLe0SA" /> <input id="recaptcha-v3-token--2" class="recaptcha-v3-token" data-recaptcha-v3-action="learn_from_us" data-recaptcha-v3-site-key="6LeN-0khAAAAAMTraqYaGpVfrIZpdkJ29ps9gCHp" data-drupal-selector="edit-captcha-response" type="hidden" name="captcha_response" value="" /> <input data-drupal-selector="edit-is-recaptcha-v3" type="hidden" name="is_recaptcha_v3" value="1" /> </div> </div> <div class="form-actions--align-left mt-auto mb-lg-4 mb-3 form-actions webform-actions js-form-wrapper form-wrapper" data-drupal-selector="edit-actions" id="edit-actions--3"> <input class="webform-button--submit button button--primary js-form-submit form-submit btn btn-outline-blue-medium" data-drupal-selector="edit-actions-submit-2" data-disable-refocus="true" type="submit" id="edit-actions-submit--2" name="op" value="Submit" /> </div> <input autocomplete="off" data-drupal-selector="form-yjh9ieqh7jn7pwtj1jzy17spnndbiwvnqicwli07oww" type="hidden" name="form_build_id" value="form-YjH9IeqH7jN7pWtj1Jzy17SPnndbIwvNqICWLI07oWw" /> <input data-drupal-selector="edit-webform-submission-learn-from-us-node-2705-add-form-2" type="hidden" name="form_id" value="webform_submission_learn_from_us_node_2705_add_form" /> </form> </div> <div id="block-acton" data-block-plugin-id="block_content:17778fd1-27fe-4ec9-b6f7-9581028f09ff" class="block-type--basic block-uuid--17778fd1-27fe-4ec9-b6f7-9581028f09ff"> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><p><script> <!--//--><![CDATA[// ><!-- (function(w,a,b,d,s){w[a]=w[a]||{};w[a][b]=w[a][b]||{q:[],track:function(r,e,t){this.q.push({r:r,e:e,t:t||+new Date});}};var e=d.createElement(s);var f=d.getElementsByTagName(s)[0];e.async=1;e.src='//marketing.imagexmedia.com/cdnr/forpci2/acton/bn/tracker/43677';f.parentNode.insertBefore(e,f);})(window,'ActOn','Beacon',document,'script');ActOn.Beacon.track(); //--><!]]> </script></p> </div> </div> </div> <div class="col-12 col-lg offset-lg-1"> <div class="row"> <div class="col footer-nav"> <div id="block-footer-navigation" data-block-plugin-id="system_menu_block:footer"> <ul data-block="footer" class="navbar-nav ms-0"> <li class="nav-item"> <a class="nav-link transition__ease-link py-1 mb-1" href="https://imagexmedia.com/services">Services</a> </li> <li class="nav-item"> <a class="nav-link transition__ease-link py-1 mb-1" href="https://imagexmedia.com/work">Work</a> </li> <li class="nav-item"> <a class="nav-link transition__ease-link py-1 mb-1" href="https://imagexmedia.com/about">About</a> </li> <li class="nav-item"> <a class="nav-link transition__ease-link py-1 mb-1" href="https://imagexmedia.com/blog">Insights</a> </li> <li class="nav-item"> <a class="nav-link transition__ease-link py-1 mb-1" href="https://imagexmedia.com/careers">Careers</a> </li> <li class="nav-item"> <a class="nav-link transition__ease-link py-1 mb-1" href="https://imagexmedia.com/contact">Let's Chat</a> </li> </ul> </div> </div> <div class="col offset-lg-1 social-media-footer"> <div id="block-socialmedia" data-block-plugin-id="block_content:15933712-2bac-4747-8b7b-fb14a6ac8f37" class="block-type--basic block-uuid--15933712-2bac-4747-8b7b-fb14a6ac8f37"> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><div class="row row-cols-2 row-cols-sm-3 gy-4 text-start pt-0"><a href="https://dribbble.com/ImageXMedia" rel="noopener noreferrer" target="_blank"><img alt="Driblle" class="px-2 m-0 icon" src="https://imagexmedia.com/blog/accessible-forms-in-drupal/data:image/svg+x..." /></a> <a href="https://clutch.co/profile/imagex" rel="noopener noreferrer" target="_blank"> <img alt="Clutch" class="px-2 m-0 icon" src="https://imagexmedia.com/blog/accessible-forms-in-drupal/data:image/svg+x..." /></a> <a href="https://www.youtube.com/channel/UC-Ho4ghPm3RsoMKpSIF9tyQ" rel="noopener noreferrer" target="_blank"> <img alt="Youtube" class="px-2 m-0 icon" src="https://imagexmedia.com/blog/accessible-forms-in-drupal/data:image/svg+x..." /></a> <a href="https://www.facebook.com/imagexmedia" rel="noopener noreferrer" target="_blank"> <img alt="Facebook" class="px-2 m-0 icon" src="https://imagexmedia.com/blog/accessible-forms-in-drupal/data:image/svg+x..." /></a> <a href="https://twitter.com/imagex_media" rel="noopener noreferrer" target="_blank"> <img alt="Twitter" class="px-2 m-0 icon" src="https://imagexmedia.com/blog/accessible-forms-in-drupal/data:image/svg+x..." width="40" /></a> <a href="https://www.linkedin.com/company/imagex-media" rel="noopener noreferrer" target="_blank"> <img alt="Linkedin" class="px-2 m-0 icon" src="https://imagexmedia.com/blog/accessible-forms-in-drupal/data:image/svg+x..." /></a> <a href="https://www.drupal.org/imagex" rel="noopener noreferrer" target="_blank"> <img alt="Drupal" class="px-2 m-0 icon" src="https://imagexmedia.com/blog/accessible-forms-in-drupal/data:image/svg+x..." /></a> <a href="https://www.instagram.com/imagex_media/" rel="noopener noreferrer" target="_blank"> <img alt="Instagram" class="px-2 m-0 icon" src="https://imagexmedia.com/blog/accessible-forms-in-drupal/data:image/svg+x..." /></a></div> </div> </div> </div> </div> </div> </div> </div> <div class="row justify-content-between align-items-center"> <div class="col-auto col-lg-auto"> <a class="logo" href="https://imagexmedia.com/"> <?xml version="1.0" encoding="UTF-8"?> <svg width="170px" height="49px" viewBox="0 0 170 49" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <title>26DB5168-0966-4D82-91CD-748DAD1498D5</title> <g id="IMX-website-2021" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="why_drupal-(Fri-17-Sep)-" transform="translate(-159.000000, -53.000000)"> <g id="Group" transform="translate(159.000000, 53.000000)"> <path d="M47.0827836,38 L47.0827836,22.4713288 C47.0827836,20.3660355 46.6848133,18.612135 44.0451613,18.612135 C41.4055092,18.612135 40.9079377,20.3660355 40.9079377,22.4713288 L40.9079377,38 L33.0916274,38 L33.0916274,22.4713288 C33.0916274,20.3660355 32.6932222,18.612135 30.003987,18.612135 C27.3647698,18.612135 26.7680319,20.3660355 26.7680319,22.4713288 L26.7680319,38 L19,38 L19,12.4507279 L22.5351939,12.4507279 L24.3780355,15.9077674 C25.5232331,13.8541109 27.9628126,12 32.1447626,12 C35.2319681,12 37.822037,12.9010183 39.4648061,15.7077842 C40.2120333,14.4046116 42.4019572,12 46.6848133,12 C53.8047843,12 55,16.8604225 55,22.6196752 L55,38 L47.0827836,38" id="Fill-1" fill="#FFFFFF"></path> <path d="M70.2001712,28.2093804 C67.1001205,28.5175945 66.1501895,29.4377699 66.1501895,30.6639259 C66.1501895,32.2505584 67.3503782,33.374423 70.2001712,33.374423 C73.6994113,33.374423 75.1494209,30.8180329 75.1996471,27.5974191 L70.2001712,28.2093804 Z M79.4994497,38.5399123 L77.6493772,34.4491521 C75.8997572,37.415601 73.1495432,39 68.6003564,39 C62.6002865,39 58,36.4436099 58,30.9725866 C58,25.9612044 61.3507451,23.864902 68.5003407,23.0961535 L75.1996471,22.3801142 C75.1996471,19.3636364 74.1003477,17.7792373 70.6998131,17.7792373 C67.4997467,17.7792373 66.9005258,18.8512863 66.6004787,20.3856564 L59.000594,20.3856564 C59.2495414,16.0907602 61.1996296,12 70.4993449,12 C81.3997484,12 83,17.0109356 83,23.7603772 L83,38.5399123 L79.4994497,38.5399123 L79.4994497,38.5399123 Z" id="Fill-2" fill="#FFFFFF"></path> <path d="M98.6029873,18.1834075 C95.4273188,18.1834075 94.6337267,19.5628552 94.6337267,21.6579192 C94.6337267,23.9596771 95.3792092,25.4931408 98.6536974,25.4931408 C101.829366,25.4931408 102.572248,23.9596771 102.572248,21.6579192 C102.572248,19.6150867 101.928186,18.1834075 98.6029873,18.1834075 Z M99.1989398,49 C88.7795226,49 86.2483497,46.3433356 86,41.1313449 L93.6901714,41.1313449 C93.9892312,42.9706085 94.4837634,43.7357778 99.2977596,43.7357778 C103.366273,43.7357778 104.010335,42.1005297 104.010335,41.1808979 C104.010335,40.2612662 103.11749,40.1594817 101.332233,39.8536818 L94.930186,38.8300334 C90.3671401,38.1166492 86.2483497,36.8889854 86.2483497,32.8001955 C86.2483497,30.3993316 88.18357,28.6100674 90.9128159,27.8940047 C87.9854971,26.7708039 86.7935921,24.0614616 86.7935921,21.4543502 C86.7935921,16.2423595 89.1271254,12 100.041075,12 L111.502867,12 L111.502867,15.5762961 L107.584317,17.5200227 C109.072681,18.1834075 110.561912,19.9208866 110.561912,22.527998 C110.561912,27.5882048 108.279089,30.5511155 97.9584917,30.5511155 C93.9892312,30.5511155 93.5909182,31.1153944 93.5909182,31.7787792 C93.5909182,32.544395 93.938521,33.0037644 99.9422551,34.0278592 C103.863406,34.6916905 105.004601,34.8952595 105.004601,34.8952595 C108.476729,35.5095378 112,36.6327385 112,40.7215285 C112,44.809872 109.321464,49 99.1989398,49 L99.1989398,49 Z" id="Fill-3" fill="#FFFFFF"></path> <path d="M127.524119,17.8292663 C124.46663,17.8292663 123.037591,19.8759864 122.790804,22.6360658 L132.010216,22.6360658 C132.010216,19.9255687 130.778004,17.8292663 127.524119,17.8292663 Z M128.06593,39 C117.515677,39 115,32.352403 115,25.5011167 C115,18.7494416 117.565638,12 127.919495,12 C137.582521,12 140,18.9035487 140,25.754835 C140,26.5231367 140,27.2892051 139.898787,28.0575068 L122.742566,28.0575068 C122.790804,31.5340723 124.368432,33.374423 127.723099,33.374423 C130.532939,33.374423 131.518365,32.4542477 131.81382,31.4322276 L139.85098,31.4322276 C139.258778,35.5730168 135.710299,39 128.06593,39 L128.06593,39 Z" id="Fill-4" fill="#FFFFFF"></path> <path d="M9.50022798,9 C11.9856122,9 14,6.98515629 14,4.49977202 C14,2.01484371 11.9856122,0 9.50022798,0 C7.01484371,0 5,2.01484371 5,4.49977202 C5,6.98515629 7.01484371,9 9.50022798,9" id="Fill-6" fill="#FFFFFF"></path> <polyline id="Fill-8" fill="#FFFFFF" points="12.9846641 12 0 12 0 15.6758191 5.12568674 17.7987949 5.12568674 38 13 38 13 12.0151323 12.947858 12.0151323 12.9846641 12"></polyline> <polyline id="Fill-9" fill="#959595" opacity="0.711635045" points="152 25.0329599 162.149439 12 170 12 159.650922 25.0329599 170 38 162.149439 38 152 25.0329599"></polyline> <polyline id="Fill-10" fill="#FF1A39" points="149.850316 38 142 38 152.348893 25.0329599 142 12 149.850316 12 160 25.0329599 149.850316 38"></polyline> </g> </g> </g> </svg> </a> </div> <div class="col col-lg-auto font-size-sm text-end"> © 2024 ImageX. <a class="link-light" href="https://imagexmedia.com/privacy-policy">Privacy Policy</a> </div> </div> </div> </div> </div> </footer> </div> </div> <span class="d-none icons-sprite"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><symbol class="icon__arrow-long" viewBox="0 0 67 20" id="arrow-right-long"><path d="m56.142 17.478 5.727-5.836H1.551A1.555 1.555 0 0 1 0 10.062C0 9.21.656 8.48 1.551 8.48h60.318l-5.727-5.836a1.562 1.562 0 0 1 0-2.189 1.492 1.492 0 0 1 2.148 0l8.292 8.45c.299.304.418.73.418 1.094 0 .426-.12.79-.418 1.094l-8.292 8.45a1.492 1.492 0 0 1-2.148 0c-.596-.608-.596-1.459 0-2.066z" /></symbol><symbol class="icon__arrow-short" viewBox="0 0 27 17" id="arrow-right-short"><path d="m17.288 14.856 5.123-4.96H1.387C.64 9.895 0 9.325 0 8.551c0-.723.587-1.343 1.387-1.343h21.024l-5.123-4.961a1.28 1.28 0 0 1 0-1.86 1.384 1.384 0 0 1 1.921 0l7.417 7.182c.267.259.374.62.374.93 0 .362-.107.672-.374.93l-7.417 7.182a1.384 1.384 0 0 1-1.92 0c-.534-.516-.534-1.24 0-1.756z" /></symbol><symbol viewBox="0 0 25 14" id="chevron-down"><path d="m3.153.554 7.295 7.093 1.976 1.918 1.975-1.918L21.694.554a1.965 1.965 0 0 1 2.736 0c.76.74.76 1.92 0 2.66L13.867 13.483c-.38.37-.911.517-1.367.517-.532 0-.988-.148-1.367-.517L.57 3.213a1.836 1.836 0 0 1 0-2.659c.76-.738 1.823-.738 2.583 0Z" /></symbol><symbol viewBox="0 0 57 59" id="design"><path d="M56.11 11.155a.85.85 0 0 0 .349-.08.902.902 0 0 0 .292-.22.974.974 0 0 0 .19-.322c.043-.12.063-.248.058-.377V.992a1.019 1.019 0 0 0-.058-.376.969.969 0 0 0-.19-.321.897.897 0 0 0-.293-.217A.846.846 0 0 0 56.11 0h-8.954a.9.9 0 0 0-.657.29 1.03 1.03 0 0 0-.274.702v3.594h-35.45V.992a1.034 1.034 0 0 0-.274-.702.9.9 0 0 0-.657-.29H.89a.846.846 0 0 0-.348.078.897.897 0 0 0-.293.217.97.97 0 0 0-.19.321.998.998 0 0 0-.058.376v9.165a1 1 0 0 0 .248.699.903.903 0 0 0 .292.219.85.85 0 0 0 .349.08h3.513v36.19h1.854v-36.19h3.587a.906.906 0 0 0 .658-.294 1.04 1.04 0 0 0 .273-.705V6.57h35.45v3.587c0 .264.099.518.273.705.174.187.41.292.658.294h3.587v36.19h-3.587a.906.906 0 0 0-.658.295 1.04 1.04 0 0 0-.273.704v3.587h-35.45v-3.587c0-.264-.099-.517-.273-.704a.906.906 0 0 0-.658-.294H.89a.852.852 0 0 0-.349.08.905.905 0 0 0-.292.218.976.976 0 0 0-.19.323c-.043.12-.063.248-.058.377v9.165a.996.996 0 0 0 .058.375.97.97 0 0 0 .19.322.897.897 0 0 0 .293.216c.11.05.228.077.348.078h8.954a.9.9 0 0 0 .657-.29 1.03 1.03 0 0 0 .274-.701v-3.595h35.45v3.595c.001.263.1.515.274.701a.9.9 0 0 0 .657.29h8.954c.12 0 .238-.027.348-.078.11-.05.21-.124.293-.216a.97.97 0 0 0 .19-.322.996.996 0 0 0 .058-.375v-9.165a1.023 1.023 0 0 0-.058-.377.974.974 0 0 0-.19-.323.904.904 0 0 0-.292-.218.852.852 0 0 0-.349-.08h-3.513V11.154h3.513ZM8.921 9.172H1.854V1.983h7.067v7.189Zm0 47.345H1.854v-7.189h7.067v7.19Zm46.225 0h-7.067v-7.189h7.067v7.19ZM48.079 1.983h7.067v7.189h-7.067V1.983Z" /></symbol><symbol viewBox="0 0 50 58" id="develop"><path d="m35.614 32.105 3.216-3.387-3.216-3.386 1.613-1.687 3.216 3.386 1.602 1.687-1.602 1.688-3.216 3.386-1.613-1.687Zm-22.841-8.46L9.557 27.03l-1.602 1.687 1.602 1.688 3.216 3.386 1.613-1.687-3.215-3.387 3.215-3.386-1.613-1.687ZM50 14.359v28.718l-25 14.36-25-14.36V14.36L25 0l25 14.36Zm-2.273 1.424L25 2.728 2.273 15.783v25.87L25 54.709l22.727-13.054v-25.87ZM21.466 38.83l9.09-19.146-2.022-1.077-9.09 19.146 2.022 1.077Z" /></symbol><symbol viewBox="0 0 52 55" id="evolve"><path d="m51.668 1.123-.266-.098c-4.305-1.568-9.151-1.329-13.298.658-1.444.691-2.579 1.485-3.47 2.427-2.264 2.394-2.81 6.042-1.434 8.973l-2.594 1.769V2.134c0-.476-.366-.863-.82-.863-.452 0-.819.387-.819.863V28.32l-4.174-2.935c2.027-3.897 1.398-8.89-1.63-12.178-1.175-1.277-2.683-2.364-4.61-3.32-5.56-2.761-12.1-3.18-17.937-1.146l-.306.15L0 9.39l.02.293c.46 6.46 3.334 12.663 7.889 17.015 1.577 1.508 3.107 2.559 4.679 3.214a9.474 9.474 0 0 0 3.645.731c2.937 0 5.774-1.382 7.636-3.829l4.868 3.423a.76.76 0 0 0 .23.11v10.641c-8.158.346-14.668 5.764-14.668 12.395v.864h30.975v-.864c0-6.63-6.51-12.05-14.668-12.395v-24.09c.02-.01.043-.016.063-.03l3.422-2.336a7.258 7.258 0 0 0 5.704 2.804c.887 0 1.785-.166 2.65-.512 1.181-.472 2.334-1.24 3.523-2.344 3.415-3.175 5.604-7.738 6.007-12.52L52 1.666l-.332-.542ZM23.421 24.42 10.9 15.615a.794.794 0 0 0-1.137.24.892.892 0 0 0 .227 1.197l12.52 8.804c-2.206 2.798-5.993 3.833-9.322 2.447-1.383-.576-2.75-1.52-4.18-2.886-4.097-3.915-6.737-9.435-7.301-15.228C7 8.515 12.85 8.965 17.853 11.449c1.745.867 3.097 1.835 4.133 2.96 2.493 2.707 3.038 6.79 1.435 10.01Zm20.17 28.098H15.982c.57-5.498 6.544-9.837 13.805-9.837 7.26 0 13.233 4.339 13.804 9.837Zm1.293-39.333c-1.039.966-2.026 1.628-3.02 2.025-2.281.91-4.832.216-6.381-1.627l9.027-6.16a.893.893 0 0 0 .242-1.194.796.796 0 0 0-1.134-.256l-9.027 6.16c-.963-2.244-.519-4.983 1.203-6.803.749-.791 1.726-1.47 2.987-2.074a15.678 15.678 0 0 1 6.767-1.53c1.597 0 3.195.243 4.727.731-.5 4.107-2.442 7.987-5.39 10.728Z" /></symbol><symbol viewBox="0 0 54 53" id="review"><path d="M48.8 0H5.17C2.33 0 0 2.285 0 5.072v31.376c0 2.786 2.33 5.072 5.17 5.072h3.579v6.854c0 1.895 1.136 3.511 2.897 4.236.625.223 1.193.39 1.818.39a4.692 4.692 0 0 0 3.295-1.337l10.283-10.088h21.701c2.84 0 5.17-2.285 5.17-5.071V5.072C53.97 2.285 51.64 0 48.8 0zm2.159 36.392c0 1.17-.966 2.118-2.16 2.118H26.475c-.398 0-.796.167-1.08.446L14.657 49.489c-.739.725-1.59.502-1.875.39-.227-.111-1.08-.502-1.08-1.56v-8.304c0-.836-.68-1.45-1.476-1.45H5.17c-1.193 0-2.16-.947-2.16-2.117V5.07c0-1.17.967-2.117 2.16-2.117H48.8c1.193 0 2.159.947 2.159 2.118v31.32z" /><path d="m37.341 16.088-5.32-.74-2.461-4.9a2.355 2.355 0 0 0-2.117-1.31c-.915 0-1.716.513-2.117 1.31l-2.346 4.786-5.435.797c-.858.114-1.602.74-1.888 1.595a2.316 2.316 0 0 0 .572 2.393l3.89 3.76-.915 5.412c-.171.855.23 1.766.916 2.279.743.513 1.659.57 2.46.17l4.749-2.506 4.863 2.564c.343.17.744.285 1.087.285a2.47 2.47 0 0 0 1.373-.456c.744-.513 1.087-1.367.916-2.279l-.916-5.298 3.948-3.817c.63-.627.858-1.538.572-2.393-.229-.911-.915-1.538-1.83-1.652zm-5.15 6.039c-.457.456-.686 1.14-.571 1.766l.744 4.444-4.12-2.165c-.286-.171-.63-.228-.915-.228-.344 0-.63.057-.916.228l-4.005 2.108.801-4.558c.115-.627-.114-1.31-.572-1.766l-3.261-3.133 4.577-.684c.63-.114 1.201-.513 1.488-1.082l2.002-4.045 2.06 4.159c.286.57.858 1.025 1.487 1.082l4.463.627-3.261 3.247z" /></symbol><symbol viewBox="0 0 43 50" id="strategy"><path d="M6.688 0h29.049c1.8 0 3.437.735 4.622 1.916a6.503 6.503 0 0 1 1.92 4.613v36.664a6.5 6.5 0 0 1-1.92 4.612 6.533 6.533 0 0 1-4.623 1.916H6.689c-1.8 0-3.437-.735-4.623-1.916a6.503 6.503 0 0 1-1.92-4.612V6.529a6.5 6.5 0 0 1 1.92-4.613A6.532 6.532 0 0 1 6.688 0Zm1.681 13.191a1.166 1.166 0 0 1-1.632-.218 1.16 1.16 0 0 1 .22-1.63l5-3.802a1.165 1.165 0 0 1 1.431.013l4.986 3.79c.51.389.61 1.118.218 1.63-.39.51-1.121.607-1.632.217l-3.128-2.376v8.69c0 .696.186 1.333.526 1.864l.035.057c.34.507.83.924 1.442 1.205l12.191 5.611a5.758 5.758 0 0 1 2.462 2.119l.045.074c.553.894.856 1.935.856 3.046v7.78a1.167 1.167 0 0 1-2.336 0v-7.78c0-.674-.174-1.294-.491-1.813l-.035-.051a3.457 3.457 0 0 0-1.477-1.264l-12.191-5.61a5.77 5.77 0 0 1-2.414-2.05l-.049-.07a5.736 5.736 0 0 1-.901-3.12v-8.69L8.367 13.19l.002.002Zm4.04 18.212c1.505 0 2.87.61 3.856 1.594a5.419 5.419 0 0 1 1.598 3.848c0 1.503-.61 2.863-1.598 3.85a5.443 5.443 0 0 1-3.856 1.593 5.443 5.443 0 0 1-3.857-1.593 5.421 5.421 0 0 1-1.599-3.85c0-1.502.61-2.863 1.599-3.848a5.444 5.444 0 0 1 3.857-1.593Zm2.206 3.243a3.113 3.113 0 0 0-2.206-.912 3.113 3.113 0 1 0 0 6.225c.862 0 1.641-.348 2.206-.911a3.1 3.1 0 0 0 .914-2.201 3.1 3.1 0 0 0-.914-2.201Zm12.809-16.598a1.17 1.17 0 0 1-1.653 0 1.162 1.162 0 0 1 0-1.647l3.258-3.25-3.258-3.25a1.164 1.164 0 0 1 0-1.648 1.17 1.17 0 0 1 1.653 0l3.257 3.25 3.257-3.25a1.17 1.17 0 0 1 1.652 0 1.164 1.164 0 0 1 0 1.649l-3.257 3.25 3.257 3.25a1.162 1.162 0 0 1 0 1.646 1.17 1.17 0 0 1-1.652 0l-3.257-3.25-3.257 3.25Zm8.314-15.716H6.689a4.198 4.198 0 0 0-2.97 1.234 4.183 4.183 0 0 0-1.237 2.966v36.664c0 1.153.474 2.202 1.237 2.965a4.198 4.198 0 0 0 2.97 1.234h29.049a4.198 4.198 0 0 0 2.972-1.234 4.183 4.183 0 0 0 1.237-2.965V6.532a4.186 4.186 0 0 0-1.237-2.966 4.202 4.202 0 0 0-2.972-1.234Z" /></symbol><symbol viewBox="0 0 49 49" id="time"><path d="M24.5 0a24.5 24.5 0 1 0 0 49 24.5 24.5 0 0 0 0-49zm0 44.917a20.417 20.417 0 1 1 0-40.834 20.417 20.417 0 0 1 0 40.834zm8.595-14.72a2.044 2.044 0 0 1 .116 2.994 2.044 2.044 0 0 1-2.994-.116l-7.146-7.146a2.042 2.042 0 0 1-.613-1.429V10.208a2.042 2.042 0 0 1 4.084 0v13.455l6.553 6.533z" /></symbol><symbol viewBox="0 0 65 65" id="values-ae"><path d="M38.394 36.97c.331 0 .596.064.927.064 4.371 0 8.61-1.684 11.788-4.793 3.51-3.433 5.232-8.097 4.835-12.89l-.066-.712-.729-.065c-4.9-.389-9.735 1.295-13.245 4.728-3.378 3.304-5.166 7.902-4.834 12.631l-3.643 3.563V29.131c3.576-3.11 5.63-7.514 5.63-12.178 0-4.793-2.186-9.327-5.961-12.436L32.5 4l-.596.453c-3.775 3.11-5.96 7.644-5.96 12.437 0 4.663 2.053 9.133 5.629 12.177v16.452l-3.709-3.627a16.081 16.081 0 0 0-4.834-12.436c-3.51-3.433-8.278-5.117-13.18-4.729l-.728.065-.066.712a16.183 16.183 0 0 0 4.835 12.955 16.886 16.886 0 0 0 11.788 4.793c.397 0 .728 0 1.126-.065l4.9 4.794v11.335H15.281c-.463 0-.86.389-.86.842 0 .453.397.842.86.842h34.305c.464 0 .861-.389.861-.842 0-.453-.397-.842-.86-.842H33.36v-3.11l3.113-3.044h.728c2.914 0 5.762-1.165 7.88-3.173 2.319-2.267 3.51-5.441 3.246-8.615l-.066-.713-.729-.064c-3.311-.26-6.49.842-8.808 3.109-2.185 2.137-3.311 5.052-3.245 8.031l-2.053 2.008V41.828l4.967-4.858Zm4.702-12.502a15.053 15.053 0 0 1 10.994-4.275c.132 4.016-1.391 7.902-4.371 10.752-2.583 2.527-6.027 4.016-9.67 4.21l8.742-8.55a.887.887 0 0 0 0-1.23c-.33-.324-.927-.324-1.258 0l-8.742 8.744c.133-3.627 1.656-7.06 4.305-9.65Zm-28.013 12.76c-2.914-2.85-4.504-6.736-4.371-10.752 4.106-.13 8.08 1.36 10.993 4.275a14.333 14.333 0 0 1 4.305 9.457l-8.61-8.55c-.33-.324-.927-.324-1.258 0a.886.886 0 0 0 0 1.23l8.808 8.615c-3.708-.194-7.218-1.619-9.867-4.275Zm24.834 7.708c1.788-1.748 4.106-2.655 6.557-2.655h.066c0 2.396-.994 4.728-2.715 6.477-1.789 1.749-4.106 2.656-6.623 2.656-.066-2.397.927-4.729 2.715-6.478ZM27.732 16.954c0-4.08 1.722-7.902 4.702-10.622 2.98 2.72 4.702 6.607 4.702 10.622 0 3.628-1.39 7.06-3.841 9.716V14.428c0-.453-.398-.906-.861-.906-.464 0-.861.388-.861.906V26.67c-2.45-2.655-3.841-6.088-3.841-9.716Z" /></symbol><symbol viewBox="0 0 65 65" id="values-ao"><path d="M32.6 4.171c-.276.01-.558.04-.827.098-2.15.47-3.817 2.422-3.937 4.863-.274 7.223-1.209 11.504-5.69 17.954-.56-.892-1.54-1.496-2.657-1.496H8.15C6.428 25.59 5 27.016 5 28.74v22.678c0 1.723 1.427 3.15 3.15 3.15h11.34c1.086 0 2.05-.567 2.617-1.418.655.528 1.262 1.002 1.89 1.378 1.039.622 2.213.996 3.82 1.162 3.212.333 8.483.087 20.65.138.286-.007.569-.112.788-.296 2.448-1.997 3.512-3.91 3.996-6.063.238-.073.454-.22.61-.413 1.831-2.289 2.494-4.93 2.067-7.206.176-.08.332-.203.453-.354 2.139-2.509 2.571-5.722 1.812-8.15 1.306-1.545 1.846-3.358 1.653-5.04-.308-2.128-1.854-4.267-3.82-4.606h-16.22c.97-6.38.326-12.245-1.831-16.045-1.166-2.05-2.864-3.199-4.548-3.445-.28-.04-.55-.05-.827-.039Zm3.19 4.725c1.842 3.244 2.607 9.313 1.358 15.828-.139.726.5 1.497 1.24 1.496h17.284c.946.485 1.554 1.32 1.674 2.362.116 1.009-.295 2.195-1.32 3.307h-6.929c-.666 0-1.278.595-1.278 1.26 0 .666.613 1.27 1.278 1.26h6.772c.379 1.392.088 3.244-1.161 5.04h-5.61c-.666 0-1.279.594-1.279 1.26 0 .665.613 1.269 1.278 1.26h4.252c.379 1.391.088 3.243-1.161 5.04h-3.09c-.666 0-1.279.593-1.279 1.26 0 .665.613 1.269 1.278 1.26h1.575c-.388 1.347-1.099 2.38-2.736 3.779-11.71-.04-17.14.144-19.864-.138-1.389-.144-2.041-.348-2.775-.787-.662-.396-1.473-1.095-2.658-2.048V30.688c5.854-7.445 7.427-13.705 7.717-21.458.843-3.86 4.245-2.795 5.433-.334ZM8.15 28.11h11.34c.37 0 .629.259.629.63v22.678c0 .37-.26.63-.63.63H8.15c-.37 0-.63-.26-.63-.63V28.739c0-.37.26-.63.63-.63Z" /></symbol><symbol viewBox="0 0 65 65" id="values-ec"><path d="M57.032 26.47h-8.714V10.573C48.318 8.598 46.694 7 44.712 7H7.606C5.624 7 4 8.598 4 10.572v26.372c0 1.975 1.624 3.572 3.606 3.572h3.08v8.377c0 .444.27.854.695 1.02a1.113 1.113 0 0 0 1.221-.232l9.25-9.164h4.638v8.498c0 1.62 1.332 2.94 2.968 2.94h16.867l6.787 6.723c.213.211.504.322.795.322a.946.946 0 0 0 .426-.089c.414-.166.694-.577.694-1.02v-5.947h2.005c1.635 0 2.968-1.32 2.968-2.94V29.41c0-1.62-1.333-2.94-2.968-2.94zm-35.65 11.828c-.302 0-.582.122-.795.321l-7.66 7.589v-6.812c0-.61-.505-1.11-1.12-1.11h-4.2a1.348 1.348 0 0 1-1.367-1.342V10.572c0-.743.616-1.353 1.366-1.353h37.095c.75 0 1.366.61 1.366 1.353v26.372c0 .743-.616 1.354-1.366 1.354H21.382zM57.76 49.015c0 .4-.325.721-.728.721h-3.125c-.616 0-1.12.5-1.12 1.11v4.37l-5.208-5.158a1.114 1.114 0 0 0-.795-.322H29.458a.723.723 0 0 1-.728-.721v-8.498h15.982c1.994 0 3.606-1.61 3.606-3.573V28.69h8.714c.403 0 .728.322.728.72v19.605z" /><path d="m21 24.5 4.5 3.5 9.5-9" /></symbol><symbol viewBox="0 0 65 65" id="values-qo"><path d="M31.264 60.6a.946.946 0 0 0 .165.163c.013.01.029.018.043.027.06.041.124.075.191.1a.933.933 0 0 0 .336.069.935.935 0 0 0 .336-.068.922.922 0 0 0 .191-.101c.014-.01.03-.017.044-.027.06-.047.117-.1.165-.162L54.8 32.379a.935.935 0 0 0 .036-1.1l-8.215-12.082a.933.933 0 0 0-.772-.408h-27.7a.934.934 0 0 0-.772.408L9.162 31.278a.934.934 0 0 0 .036 1.101l22.066 28.222Zm7.664-27.862L32 56.666l-6.928-23.928h13.856Zm-13.34-1.868L32 21.39l6.41 9.479H25.59Zm15.285 1.868h11.276L34.241 55.641l6.632-22.903Zm.769-1.868 4.356-9.267L52.3 30.87H41.642Zm-1.618-.95-6.264-9.263h10.619l-4.355 9.262Zm-16.049 0-4.354-9.263H30.24l-6.265 9.262Zm-1.617.95H11.7l6.302-9.267 4.356 9.267Zm.769 1.868 6.631 22.904L11.85 32.738h11.277ZM32 10.598a.934.934 0 0 0 .934-.934v-5.69a.934.934 0 1 0-1.868 0v5.69c0 .516.418.934.934.934Zm-7.886.621a.935.935 0 0 0 1.756-.64l-1.946-5.346a.935.935 0 0 0-1.755.64l1.945 5.346Zm-6 3.839a.93.93 0 0 0 1.316.115.934.934 0 0 0 .115-1.316L15.888 9.5a.935.935 0 0 0-1.43 1.2l3.656 4.359Zm27.056.334a.931.931 0 0 0 .715-.334l3.657-4.358a.934.934 0 1 0-1.43-1.201l-3.658 4.358a.934.934 0 0 0 .715 1.535Zm-6.482-3.615a.935.935 0 0 0 1.198-.558l1.945-5.346a.935.935 0 0 0-1.756-.64L38.13 10.58a.933.933 0 0 0 .558 1.198Z" /></symbol><symbol viewBox="0 0 65 65" id="values-st"><path d="M33.25 19.71h19l-5.563-7.355L52.25 5h-21.5v22L11 61h42L33.25 27v-7.29Zm0-12.13h13.875L43.5 12.356l3.625 4.774H33.25V7.581ZM32 29.904l8.438 14.452-1.376 1.935-4.25-4.387-7.187 7.355-4.313-4.452L32 29.904ZM15.437 58.42 22 47.13l5.625 5.806 7.125-7.419 4.563 4.71 2.5-3.484 6.75 11.677H15.437Z" /></symbol></defs></svg></span> <script type="application/json" data-drupal-selector="drupal-settings-json">{"path":{"baseUrl":"\/","scriptPath":null,"pathPrefix":"","currentPath":"node\/2705","currentPathIsAdmin":false,"isFront":false,"currentLanguage":"en"},"pluralDelimiter":"\u0003","suppressDeprecationErrors":true,"ajaxPageState":{"libraries":"captcha\/base,ckeditor_indentblock\/indentblock,codesnippet\/codesnippet.highlightjs,codesnippet\/codesnippet.style.github,core\/internal.jquery.form,filter\/caption,gsap\/gsap,gsap\/scrolltrigger,gsap\/text,ixm\/acton,ixm\/fonts,ixm\/global-theming,ixm\/header,ixm\/webform,recaptcha_v3\/recaptcha_v3,seven\/classy.node,system\/base,views\/views.module,webform\/webform.ajax,webform\/webform.element.details.save,webform\/webform.element.details.toggle,webform\/webform.element.message,webform\/webform.element.select,webform\/webform.form","theme":"ixm","theme_token":null},"ajaxTrustedUrl":{"form_action_p_pvdeGsVG5zNF_XLGPTvYSKCf43t8qZYSwcfZl2uzM":true,"\/blog\/accessible-forms-in-drupal?ajax_form=1":true},"ajax":{"edit-actions-submit--2":{"callback":"::submitAjaxForm","event":"click","effect":"none","speed":500,"progress":{"type":"throbber","message":""},"disable-refocus":true,"url":"\/blog\/accessible-forms-in-drupal?ajax_form=1","dialogType":"ajax","submit":{"_triggering_element_name":"op","_triggering_element_value":"Submit"}},"edit-captcha-response--2":{"callback":"recaptcha_v3_ajax_callback","event":"change","url":"\/blog\/accessible-forms-in-drupal?ajax_form=1","dialogType":"ajax","submit":{"_triggering_element_name":"captcha_response"}},"edit-actions-submit":{"callback":"::submitAjaxForm","event":"click","effect":"none","speed":500,"progress":{"type":"throbber","message":""},"disable-refocus":true,"url":"\/blog\/accessible-forms-in-drupal?ajax_form=1","dialogType":"ajax","submit":{"_triggering_element_name":"op","_triggering_element_value":"Submit"}},"edit-captcha-response":{"callback":"recaptcha_v3_ajax_callback","event":"change","url":"\/blog\/accessible-forms-in-drupal?ajax_form=1","dialogType":"ajax","submit":{"_triggering_element_name":"captcha_response"}}},"blog":{"title":"Accessibility Elements, Part 3: Accessible Forms in Drupal","body":"\u003Cp\u003E\u003Cem\u003EAuthored by:\u0026nbsp;\u003Ca href=\u0022https:\/\/imagexmedia.com\/team\/nadiia-nykolaichuk\u0022\u003ENadiia Nykolaichuk\u003C\/a\u003E\u003C\/em\u003E.\u003C\/p\u003E\r\n\r\n\u003Cp\u003EOnline forms serve a myriad of purposes, including logging into accounts, leaving comments, and registering for events, to name a few. In an inclusive web environment, it is crucial that people with disabilities, who rely on assistive technology, can do these tasks seamlessly. That\u2019s what makes accessible forms indispensable for overall website accessibility.\u0026nbsp;\u003C\/p\u003E\r\n\r\n\u003Cp\u003ESo, after overviewing \u003Ca href=\u0022https:\/\/imagexmedia.com\/blog\/accessibility-alt-text-drupal\u0022\u003Ealt text\u003C\/a\u003E and \u003Ca href=\u0022https:\/\/imagexmedia.com\/blog\/accessibility-semantic-html-wai-aria-drupal\u0022\u003Esemantic HTML and WAI-ARIA\u003C\/a\u003E in our previous posts, we are now turning our focus to accessible forms as the next topic in our series on essential accessibility elements. We will go through the nature of accessible forms, as well as some specific ways in which the Drupal CMS supports their accessibility.\u003C\/p\u003E\r\n\r\n\u003Ch2\u003EIntroduction to the world of accessible forms\u003C\/h2\u003E\r\n\r\n\u003Cp\u003EAccessible forms are easy for users with disabilities to perceive, complete, and submit. The ease of working with forms shouldn\u2019t be hampered by the fact that users rely on assistive tools like screen readers or screen magnifiers, use the keyboard only, or access your website with a touchscreen device, for example.\u003C\/p\u003E\r\n\r\n\u003Ch3\u003EA journey through a form with a screen reader\u003C\/h3\u003E\r\n\r\n\u003Cp\u003EA user navigates through an online form, often with the help of keyboard commands or gestures. A screen reader identifies the form elements such as fields, labels, headings, and associated instructions, all of which help it convey its structure. It translates the information into speech for the user or presents it as Braille.\u0026nbsp;\u003C\/p\u003E\r\n\r\n\u003Cp\u003EThe screen reader indicates which form element is currently focused, helping the user understand where exactly they are in the form. It announces the type of each field (e.g., text input, checkbox, radio button), as well as provides information about the expected input. In case a submission error occurs, the screen reader announces the error message so the user can address the issue.\u0026nbsp;\u003C\/p\u003E\r\n\r\n\u003Ch3\u003EThe key characteristics of an accessible form\u003C\/h3\u003E\r\n\r\n\u003Cul\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003ESemantic HTML.\u003C\/strong\u003E An accessible form uses semantic HTML elements such as \u0026lt;form\u0026gt;, \u0026lt;label\u0026gt;, \u0026lt;select\u0026gt;, \u0026lt;option\u0026gt;,\u0026lt;input\u0026gt;, \u0026lt;textarea\u0026gt;, and more to convey its structure. The form elements need to have a clear hierarchy.\u0026nbsp;\u003C\/li\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EWAI-ARIA.\u003C\/strong\u003E The accessibility of user interactions with the form is enhanced through the use of WAI-ARIA attributes if needed.\u003C\/li\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EDescriptive labels.\u003C\/strong\u003E Each field has a clear and descriptive label that makes its purpose clear. Labels are explicitly associated with the corresponding form fields.\u003C\/li\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EFocus and interaction indicators.\u003C\/strong\u003E An accessible form has clear indicators for the form elements that are currently in focus and for its current interaction state (e.g. \u2018Checked\u2019). This is crucial for users navigating with a keyboard or other non-mouse input methods. This includes both visually distinguishable styles for sighted users and announcements by screen readers for non-sighted users.\u003C\/li\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EClear instructions.\u003C\/strong\u003E There are clear instructions or hints that guide users through filling out the form fields. Required fields are explicitly indicated, users are able to hide or show help text, and so on.\u003C\/li\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EProper form validation and error handling.\u003C\/strong\u003E Properly structured, meaningful error messages for specific fields provide context and guidance on fixing the errors. Fields with errors are clearly highlighted.\u003C\/li\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EKeyboard accessibility.\u003C\/strong\u003E Users can navigate and interact with the entire form relying solely on the keyboard. A logical tab order and a clear indication of the focus state on the form elements greatly helps that.\u003C\/li\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EContrast and readability.\u003C\/strong\u003E Sufficient color contrast for text and form controls helps users with low vision or color blindness. An accessible form also uses readable font sizes and styles, and avoids relying solely on color to convey the information.\u003C\/li\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EResponsive design.\u003C\/strong\u003E Users with special needs have smooth experiences with form handling across various devices and screen sizes, including desktops, tablets, and smartphones.\u003C\/li\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EComfortable length.\u003C\/strong\u003E If a form is lengthy, it\u2019s better to split into multiple pages or use headings for its sections.\u003C\/li\u003E\r\n\u003C\/ul\u003E\r\n\r\n\u003Ch2\u003EAccessible forms in Drupal\u003C\/h2\u003E\r\n\r\n\u003Cp\u003EThe Drupal CMS is renowned for prioritizing web accessibility and constantly embracing relevant best practices. It\u2019s built to provide the best mobile experiences, uses HTML5 and WAI-ARIA, requires Alt text, and more. The Drupal community has done \u2014 and keeps doing \u2014 amazing work on increasing the accessibility of forms. The Drupal core themes, the Form API, the Inline Form Errors module, the Webform module, and other components and tools have introduced useful enhancements in this area. They make sure a wider range of people with disabilities can use accessible forms, and empower developers and site builders to create them.\u0026nbsp;\u0026nbsp;\u003C\/p\u003E\r\n\r\n\u003Ch3\u003EForm accessibility in Drupal\u2019s new core themes\u003C\/h3\u003E\r\n\r\n\u003Cp\u003ENew Drupal default themes, Claro for the admin experiences and Olivero for the front end, are genuinely focused on accessibility in everything, including online forms.\u0026nbsp;\u003C\/p\u003E\r\n\r\n\u003Cp\u003EHere are some examples of how form accessibility is handled in the Olivero theme. The elements have a standard look that makes them more recognizable. A color bar on the left side is used to signal the form element. The labels are displayed above each field for perfect clarity in the process of completing a form.\u003C\/p\u003E\r\n\r\n\u003Cp\u003EAll the input elements, be it text fields, checkboxes, radio buttons, or submit buttons, are created with accessibility in mind and have properly implemented states (Focus, Checked, Disabled, Hover, and more) to let users know what is happening at every moment of their interaction with the form. Each state has a distinctive style (a specific color border around the element).\u0026nbsp;\u003C\/p\u003E\r\n\u003Cimg alt=\u0022A demo of the states for the input elements in the Olivero theme.\u0022 data-caption=\u0022\u0026lt;em\u0026gt;A demo of the states for the input elements in the Olivero theme.\u0026lt;\/em\u0026gt;\u0022 data-entity-type=\u0022file\u0022 data-entity-uuid=\u00220ea1718c-66e6-42eb-a79b-395d575172d6\u0022 src=\u0022\/sites\/default\/files\/inline-images\/states-for-input-elements-in-olivero-theme.png\u0022 \/\u003E\u003Cimg alt=\u0022Focus styles for the account creation form in the Olivero theme.\u0022 data-caption=\u0022\u0026lt;em\u0026gt;Focus styles for the account creation form in the Olivero theme.\u0026lt;\/em\u0026gt;\u0022 data-entity-type=\u0022file\u0022 data-entity-uuid=\u00222d8862cd-6c1f-4d48-b510-68f93688709e\u0022 src=\u0022\/sites\/default\/files\/inline-images\/focus-styles-for-form-elements-in-olivero-theme.jpg\u0022 \/\u003E\r\n\u003Cp\u003EThe default admin theme Claro is yet another embodiment of accessibility. Among other features for accessible forms, it can boast form element sizes that \u003Ca href=\u0022https:\/\/www.drupal.org\/docs\/core-modules-and-themes\/core-themes\/claro-theme\/accessibility#s-focus-state\u0022\u003Emeet\u003C\/a\u003E the Web Content Accessibility Guidelines (WCAG) at the AA level, which makes them much more convenient for the users of touch devices. The focus styles for the text input areas, checkboxes, and other elements are consistent and clear, drawing users attention to where they are in the form.\u003C\/p\u003E\r\n\u003Cimg alt=\u0022A demo of focus styles for all form elements in the Claro theme.\u0022 data-caption=\u0022\u0026lt;em\u0026gt;A demo of focus styles for all form elements in the Claro theme.\u0026lt;\/em\u0026gt;\u0022 data-entity-type=\u0022file\u0022 data-entity-uuid=\u00228dcea8b6-52fc-4904-84cb-ddeef4cb3711\u0022 src=\u0022\/sites\/default\/files\/inline-images\/focus-indicators-for-form-elements-in-claro-theme%20.png\u0022 \/\u003E\u003Cimg alt=\u0022Focus styles for the content editing form in the Claro theme.\u0022 data-caption=\u0022\u0026lt;em\u0026gt;Focus styles for the content editing form in the Claro theme.\u0026lt;\/em\u0026gt;\u0022 data-entity-type=\u0022file\u0022 data-entity-uuid=\u002259b9ec68-cd9b-4ded-aff7-8c06a263e497\u0022 src=\u0022\/sites\/default\/files\/inline-images\/focus-styles-for-content-editing-form-in-claro-theme.jpg\u0022 \/\u003E\r\n\u003Ch3\u003EForm accessibility in the Drupal core Form API\u003C\/h3\u003E\r\n\r\n\u003Cp\u003E\u003Ca href=\u0022https:\/\/www.drupal.org\/docs\/drupal-apis\/form-api\u0022\u003EForm API (FAPI)\u003C\/a\u003E in the Drupal core is a developer-oriented set of tools for form creation and management. It is designed with accessibility in mind, supporting the best accessibility practices and handling a lot of the behind-the-scenes work to ensure compliance with accessibility standards.\u0026nbsp;\u003C\/p\u003E\r\n\r\n\u003Cp\u003EHere are at least some examples of important Form API features and related practices that enhance form accessibility:\u003C\/p\u003E\r\n\r\n\u003Cul\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EHTML5 support for form elements\u003C\/strong\u003E\u003C\/li\u003E\r\n\u003C\/ul\u003E\r\n\r\n\u003Cp\u003EThe Form API extensively supports modern semantic HTML5 elements that make forms more accessible by better conveying their structure to screen readers. For example, when Drupal 8.0 was released in 2015, the support for multiple new HTML5 elements \u003Ca href=\u0022https:\/\/www.drupal.org\/node\/1315186\u0022\u003Ewere added to FAPI\u003C\/a\u003E such as the email, telephone, URL, search, number, color, and placeholder elements, as well as the \u003Ca href=\u0022https:\/\/www.drupal.org\/node\/1953036\u0022\u003E\u2018#pattern\u2019 property\u003C\/a\u003E to provide easy field validation. That was a great start to the enhancements of the form semantics that are still going on.\u003C\/p\u003E\r\n\r\n\u003Cul\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EDetails VS. Fieldsets\u0026nbsp;\u003C\/strong\u003E\u003C\/li\u003E\r\n\u003C\/ul\u003E\r\n\r\n\u003Cp\u003EIn Drupal 7 and earlier versions, the \u2018FIELDSET\u2019 and \u2018LEGEND\u2019 HTML elements were used to group related elements together into collapsible sets of fields. With the Drupal 8 release, new HTML5 \u2018DETAILS\u2019 and \u2018SUMMARY\u2019 elements \u003Ca href=\u0022https:\/\/www.drupal.org\/node\/1852020\u0022\u003Ewere introduced\u003C\/a\u003E for collapsible fieldsets. The new elements provide a clear and meaningful structure to assistive technologies, have native support across modern browsers, are more consistent and straightforward in styling, provide built-in keyboard accessibility, and are well-suited for responsive web design.\u003C\/p\u003E\r\n\r\n\u003Cul\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EThe #title property\u003C\/strong\u003E\u003C\/li\u003E\r\n\u003C\/ul\u003E\r\n\r\n\u003Cp\u003EDrupal\u0027s Form API encourages the use of the #title property to provide clear and descriptive labels for form elements. This helps diverse audiences, including users relying on screen readers, understand the purpose of each element (e.g. \u2018#title\u2019 =\u0026gt; t(\u2018Username\u2019)).\u003C\/p\u003E\r\n\r\n\u003Cp\u003EAs part of ongoing accessibility enhancements, \u003Ca href=\u0022https:\/\/www.drupal.org\/project\/drupal\/issues\/933004\u0022\u003Eit\u2019s planned to create a new core test in the upcoming Drupal 11\u003C\/a\u003E that will check every module\u0027s forms and ensure that the relevant form elements have a #title property. The forms that don\u2019t have it won\u2019t pass the test so they can\u2019t be added to the Drupal core.\u003C\/p\u003E\r\n\r\n\u003Cul\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EThe #type property\u003C\/strong\u003E\u003C\/li\u003E\r\n\u003C\/ul\u003E\r\n\r\n\u003Cp\u003EThe #type property is another property in Drupal\u2019s Form API that\u2019s essential for accessibility. It enables developers to specify the type of form element (e.g. \u2018#type\u2019 =\u0026gt; \u2018textfield\u2019). This helps make forms more accessible by ensuring that screen readers can interpret and interact with specific form elements in a proper way.\u003C\/p\u003E\r\n\u003Cimg alt=\u0022An example of a code snippet with the #type and #title properties in a form.\u0022 data-caption=\u0022\u0026lt;em\u0026gt;An example of a code snippet with the #type and #title properties in a form.\u0026lt;\/em\u0026gt;\u0022 data-entity-type=\u0022file\u0022 data-entity-uuid=\u002239022a15-80ba-4fc1-8b28-d08a0d28f643\u0022 src=\u0022\/sites\/default\/files\/inline-images\/example-of-code-snippet-with-type-and-title-properties-in-a-form.jpg\u0022 \/\u003E\r\n\u003Cul\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EThe #description property and the aria-describedby attribute\u003C\/strong\u003E\u003C\/li\u003E\r\n\u003C\/ul\u003E\r\n\r\n\u003Cp\u003EThe #description property is used in Drupal\u0027s Form API to provide additional information or context for form elements (e.g. \u2018#description\u2019 =\u0026gt; t(\u2018Choose a unique username for your account.\u2019). This text is often displayed below the element.\u0026nbsp;\u003C\/p\u003E\r\n\r\n\u003Cp\u003EHowever, for users who rely on assistive technologies like screen readers, the visual presentation may not be sufficient. This is where the \u2018aria-describedby\u2019 attribute comes into play. It is an Accessible Rich Internet Applications (ARIA) attribute that associates an HTML element with additional descriptive text. It makes sure that the screen reader reads out the additional description when the user interacts with the specific form element. The support for the attribute in the Form API was \u003Ca href=\u0022https:\/\/www.drupal.org\/project\/drupal\/issues\/405360\u0022\u003Eintroduced\u003C\/a\u003E in Drupal 8.0, and it\u2019s a great example of WAI-ARIA attributes used in the Form API.\u0026nbsp;\u003C\/p\u003E\r\n\u003Cimg alt=\u0022An example of a code snippet with the aria-describedby attribute in a form.\u0022 data-caption=\u0022\u0026lt;em\u0026gt;An example of a code snippet with the aria-describedby attribute in a form.\u0026lt;\/em\u0026gt;\u0022 data-entity-type=\u0022file\u0022 data-entity-uuid=\u0022f5eb4af0-b266-49be-8107-1b6b972c9b9a\u0022 src=\u0022\/sites\/default\/files\/inline-images\/example-of-code-snippet-with-aria-describedby-attribute-in-form.jpg\u0022 \/\u003E\r\n\u003Cul\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EError handling\u003C\/strong\u003E\u003C\/li\u003E\r\n\u003C\/ul\u003E\r\n\r\n\u003Cp\u003EForm API provides mechanisms for \u003Ca href=\u0022https:\/\/www.drupal.org\/docs\/drupal-apis\/form-api\/introduction-to-form-api\u0022\u003Evalidating form submissions and handling errors\u003C\/a\u003E. It supports the display of form validation errors with clear error messages, helping users who rely on screen readers understand and address the errors. In Drupal 8 and later versions, the validateForm method is used for form validation.\u003C\/p\u003E\r\n\r\n\u003Cul\u003E\r\n\t\u003Cli\u003E\u003Cstrong\u003EOngoing FAPI improvements\u003C\/strong\u003E\u003C\/li\u003E\r\n\u003C\/ul\u003E\r\n\r\n\u003Cp\u003EIn addition to the above-mentioned and many other accessibility features and enhancements in FAPI, the work never stops in this area. A lot more is \u003Ca href=\u0022https:\/\/www.drupal.org\/project\/drupal\/issues\/2203649\u0022\u003Eplanned to do\u003C\/a\u003E in the near future, mostly related to cementing in the semantic relationships in FAPI, checking for form field titles, ensuring the proper management of form labels, allowing customized validation messages, using ARIA to link descriptive text, and more.\u003C\/p\u003E\r\n\r\n\u003Ch3\u003EForm accessibility in the Inline Form Errors core module\u003C\/h3\u003E\r\n\r\n\u003Cp\u003EWhen it comes to the accessibility of form error messages, the Drupal core also includes a module called \u003Ca href=\u0022https:\/\/www.drupal.org\/docs\/8\/core\/modules\/inline-form-errors\/inline-form-errors-module-overview\u0022\u003EInline Form Errors\u003C\/a\u003E (also known as IFE). The module\u2019s mission is to make sure form validation errors are displayed inline with the specific fields that need attention. A semantic alert at the top of the page lists all the fields that have an error, providing links to specific fields that require correction.\u003C\/p\u003E\r\n\r\n\u003Cp\u003EThis provides more context and clarity for error handling. Users who rely on screen readers, screen magnifiers, and keyboard only are able to find the error for the relevant field with less effort.\u003C\/p\u003E\r\n\u003Cimg alt=\u0022Error handling by the Inline Form Errors module.\u0022 data-caption=\u0022\u0026lt;em\u0026gt;Error handling by the Inline Form Errors module.\u0026lt;\/em\u0026gt;\u0022 data-entity-type=\u0022file\u0022 data-entity-uuid=\u0022aafce54c-815c-4fc1-96dc-c25e2144fe66\u0022 src=\u0022\/sites\/default\/files\/inline-images\/error-handling-by-inline-form-errors-module.png\u0022 \/\u003E\r\n\u003Cp\u003EThe Inline Form Errors module is not enabled by default in Drupal core. This is meant to keep the core installation minimal and allow site builders to make decisions based on their specific needs. The Drupal core itself provides methods for handling form errors, and developers have the flexibility to use modules like Inline Form Errors or implement custom solutions to create a more user-friendly error presentation.\u003C\/p\u003E\r\n\r\n\u003Ch3\u003EForm accessibility in the Webform contributed module\u003C\/h3\u003E\r\n\r\n\u003Cp\u003E\u003Ca href=\u0022https:\/\/www.drupal.org\/project\/webform\u0022\u003EWebform\u003C\/a\u003E, one of the most popular contributed modules of all times, provides an excellent UI with an outstanding choice of options for creating and handling online forms. The module\u2019s creator, Jacob Rockowitz, wrote an article called \u201c\u003Ca href=\u0022https:\/\/www.jrockowitz.com\/blog\/webform-diy-accessibility\u0022\u003EWebform for Drupal 8: DIY Accessibility\u003C\/a\u003E\u201d where he shared how he performed a DIY accessibility audit to properly self-assess the Webform module\u0027s accessibility as he was working on the stable module\u2019s 8.x release.\u003C\/p\u003E\r\n\r\n\u003Cp\u003EAmong other things, Jacob Rockowitz mentioned that the combination of reviewing the WCAG guidelines and comparing the feedback from two automated tools, \u003Ca href=\u0022https:\/\/wave.webaim.org\/\u0022\u003EWAVE\u003C\/a\u003E and \u003Ca href=\u0022https:\/\/pa11y.org\/\u0022\u003EPa11y\u003C\/a\u003E, helped him see some immediate issues. He found and fixed a few problems related to the proper labeling of form inputs with appropriate contextual information for screen readers.\u0026nbsp;\u003C\/p\u003E\r\n\r\n\u003Cp\u003EHe also decided to publicly define the Webform module\u2019s accessibility benchmark by adding the link to the audit results and the statement of commitment to accessibility to the project page. He encouraged the community to get involved with improving the accessibility of the Webform module and Drupal overall. He also created the \u003Ca href=\u0022https:\/\/www.drupal.org\/docs\/8\/modules\/webform\/webform-accessibility\u0022\u003EWebform Accessibility documentation page\u003C\/a\u003E on drupal.org.\u003C\/p\u003E\r\n\u003Cimg alt=\u0022Accessibility information on the Webform module\u2019s page.\u0022 data-caption=\u0022\u0026lt;em\u0026gt;Accessibility information on the Webform module\u2019s page.\u0026lt;\/em\u0026gt;\u0022 data-entity-type=\u0022file\u0022 data-entity-uuid=\u00226b15e79c-60e5-4bba-81c4-92269566b8f7\u0022 src=\u0022\/sites\/default\/files\/inline-images\/accessibility-information-on-webform-module-page.jpg\u0022 \/\u003E\r\n\u003Ch3\u003EFinal thoughts\u003C\/h3\u003E\r\n\r\n\u003Cp\u003EThere\u2019s no doubt that Drupal will remain steadfast in its unwavering dedication to accessibility. The best practices and features adopted within the CMS for all website components, including online forms, create a super favorable environment for websites to be accessible to diverse audiences. It\u2019s \u003Ca href=\u0022https:\/\/imagexmedia.com\/higher-ed-website-accessibility-user-experience\u0022\u003Emore than just about meeting the accessibility standards\u003C\/a\u003E \u2014 it\u2019s about a commitment to inclusivity and aligning with the progressive values of the modern world. With the help of a \u003Ca href=\u0022https:\/\/imagexmedia.com\/about\u0022\u003Etrustworthy Drupal partner\u003C\/a\u003E, this mission is easy to fulfill.\u003C\/p\u003E\r\n","summary":"","field_downloadable":"0","field_attachement_link":null,"recaptcha_site_key":"6LeN-0khAAAAAMTraqYaGpVfrIZpdkJ29ps9gCHp"},"user":{"uid":0,"permissionsHash":"90c2c3c3972eed0856dd699c657a545031aed22ca2a9ea67ac0c68d70aefaa38"}}</script> <script src="https://imagexmedia.com//www.google.com/recaptcha/api.js?render=6LeN-0kh..." defer async></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_dxIVcwvQ00ow..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_z3zw0MjIjGT5..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_HUpcuWgtltnv..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_a0jm3VdmdpN1..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_8Sbk5ENJXWdl..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_spuSOw4GUoe_..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_w0iO3No0CsyH..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_a5UiM5riCCx8..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_Hpe5jrMuKXrV..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_V1oRQ-kJlXBZ..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_KRjtvzl6UujB..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_mjv4qhOv2_1-..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_WmhavmnC0K35..."></script> <script src="https://imagexmedia.com//cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap..."></script> <script src="https://imagexmedia.com//cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/Scro..."></script> <script src="https://imagexmedia.com//cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/Text..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_wtJSImQ8pFM6..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_WteL5KJh2Nqz..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_9UJbVpr4ers3..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_KnxXGkJpHG8k..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_OEIO6SNO4Py3..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_CK8rnR3isPGM..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_F1_WGURKgcgU..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_r6pJi3g1gHCc..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_DrW304fuY5nI..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_ezwJb8QjKN82..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_pnMCP3qumXyM..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_5lyEgtuYzFbk..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_1woVuTYaOTc9..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_qBhNqN32qT_f..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_RpLV1j1SEgkA..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_Gx3M6ZHq_5V9..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_WM8BwJHq4Vp1..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_A1d453tsHCXS..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_PJdpp6_jlfPP..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_uT1dIUeLyNP0..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_Ts8eP3UOV6bT..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_uvW9geT6o-zL..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_5QSHZIM0nzRW..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_vOBrcij10MIZ..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_xF8bzCvYBAAt..."></script> <script src="https://imagexmedia.com/sites/default/files/js/optimized/js_dVBH66puc4Ee..."></script> </body> </html>
Dec 27 2023
Dec 27

Authored by: Nadiia Nykolaichuk.

Another notable milestone on the journey from Drupal 10 to Drupal 11 has been reached. Drupal 10.2 has been released on December 15, unfolding new capabilities for Drupal development, site building, content creation, and administrative workflows. So let’s take a closer look at some of the most remarkable new features that Drupal 10.2 brings to the table.

A note of the Drupal core’s new major release schedule

Before we move on to what’s new in Drupal 10.2, it’s worth mentioning the revised major release schedule that was introduced for the Drupal core with the arrival of Drupal 10. According to this schedule, major releases will occur biennially, in even-numbered years.

That being said, we should expect Drupal 11 to roll out in 2024. Alpha development for Drupal 11 has just been opened, so it’s a great historic point for anyone who wants to get involved in building the new major version of Drupal.

Each major version will be actively supported for approximately two years, which will then be followed by two more years of maintenance support and security coverage. The support ends when two next major versions get released. This means that Drupal 10, the current latest major release, will be supported until Drupal 12 expectedly comes out in 2026.

Staying abreast with Drupal’s pace of development

The new pace with which Drupal produces new major releases might seem a bit dizzying to many users in comparison with how slowly it used to happen in the past. For example, it took approximately four years from the release of Drupal 7 to the release of Drupal 8, and almost three years from the release of Drupal 6 to the release of Drupal 7.

However, the need to upgrade more often is no reason to worry — starting with a Drupal 8 to Drupal 9 journey, upgrades have become significantly easier, and are getting even smoother over time.

If your website is still on Drupal 7, it might require a lengthier upgrade process, but that’s no problem for experts. Drupal 7’s support has been extended several times because the Drupal community strives to support numerous Drupal 7 owners. However, this time, the Drupal 7 end-of-life date is final — January 2025, so it's a last call to reach out to a Drupal team and schedule your upgrade. 

The treasures of Drupal 10.2: exploring what’s new

1. New features in Drupal 10.2 for general website administration

Announcements Feed is now a stable core module

Welcome Announcement Feed, a new stable module in the Drupal core! It was added to Drupal 10.1 as an experimental module but has reached stability in Drupal 10.2. So now it’s fully ready to inform users about the latest Drupal announcements directly from drupal.org.

Across all pages of the Drupal admin UI, you’ll see an “Announcements” link in the top right corner. By clicking it, you open a feed of announcements. Each piece has a link that expands its full drupal.org version. You can decide which user roles should see the announcement feed by using the new “View drupal.org announcements” permission in People > Permissions.

Announcements from drupal.org provided by the new Announcement Feed module in Drupal 10.2.Announcements from drupal.org provided by the new Announcement Feed module in Drupal 10.2.

Easier search for permissions

The above-mentioned People > Permissions page carries a vital mission of helping website administrators add granular permissions for user roles. Drupal 10.2 adds more user-friendliness to the page by introducing a search filter to quickly find the needed permissions. 

The new search filter on the Permissions page in Drupal 10.2.The new search filter on the Permissions page in Drupal 10.2.

The Tour module getting ready for removal

As part of the effort to make the Drupal core slimmer, the Tour module is getting removed as it wasn’t commonly used by the target audience. Starting with Drupal 10.2, it’s no longer enabled by default in the Standard installation, and is slated for removal in Drupal 11.

The Help Topics module replaced by the Help module

As of Drupal 10.2, the Help Topics module will no longer be needed. Its functionality has been moved to the Drupal core Help module.

2. New features in Drupal 10.2 for content authoring

A new UI for managing media revisions

The arsenal of editor-friendly features offered by the Media and the Media Library modules in Drupal is expanding. A new UI for managing media revisions has been added in Drupal 10.2. Previously, this kind of functionality was available for content nodes, but not for media entities, so it’s an impactful change.

Media revisions are versions of a media item that Drupal saves for you every time any edits are made. This makes it possible to track the history of edits and revert to an earlier version when needed.

The new UI enables you view the revisions of media items, revert to a specific revision, or delete a revision. Previously, these actions required the use of the Media Revision UI module, but it is no longer needed.

The new revisions page for media items in Drupal 10.2.The new revisions page for media items in Drupal 10.2.Reverting to a media revision in Drupal 10.2.Reverting to a media revision in Drupal 10.2.

There also are relevant permissions on the People > Permissions page to configure who can view media revisions pages, revert to revisions, and delete revisions. These permissions are configurable for specific media types (Audio, Remove video, Image, etc.).

Media revision permissions in Drupal 10.2.Media revision permissions in Drupal 10.2.

The “Show blocks” button in CKEditor 5

CKEditor 5 now enables you to highlight the blocks you have in your text area using HTML tags (“P” for paragraph, “H” for heading, etc.). This is due to the “Show blocks” button that appeared in Drupal 10.2. This feature is not brand-new — it was present in CKEditor 4 and has finally been reintroduced in CKEditor 5.

How “Show blocks” feature works in Drupal 10.2’s CKEditor 5.How “Show blocks” feature works in Drupal 10.2’s CKEditor 5.

The button is not in the default setup — you’ll need to enable it by dragging it from the “Available buttons” to the “Active toolbar” on the “Text formats and editors” page. We described the process of adding buttons to the toolbar in more detail in our overview of CKEditor 5

Adding the “Show blocks” button to the CKEditor toolbar.Adding the “Show blocks” button to the CKEditor toolbar.

3. New features in Drupal 10.2 for site building

Improved field selection

Adding new fields to content types or other entity types has become more intuitive and efficient in Drupal 10.2. As you start adding a new field, you see a revamped field selection UI where all fields are grouped by type. The field types are displayed as cards with visual icons, radio buttons, and brief descriptions. As you select a specific field type, more options appear below, accompanied by explanations and examples. 

The new field selection UI in Drupal 10.2.The new field selection UI in Drupal 10.2.

New “add child” link for menus and taxonomy

Managing menus and taxonomy vocabularies is getting easier thanks to the new “add child” link that has appeared in the operations dropdown for menu items and taxonomy terms.

The new “add child” link in the dropdown for menu items and taxonomy terms in Drupal 10.2.The new “add child” link in the dropdown for menu items and taxonomy terms in Drupal 10.2.

Filename sanitization configurable through the UI

Drupal 10.2 brings improvements to filename sanitization — the process of validating and cleaning up filenames to ensure they adhere to a set of rules or restrictions.

There is now a user interface at Configuration > Media > File system that enables site builders to opt-in for various forms of filename sanitization. The options include transliterating filenames, replacing whitespace, non-alphanumeric characters, and sequences of dots, underscores, and dashes, and converting filenames to lowercase.

The new UI for managing filename sanitization in Drupal 10.2.The new UI for managing filename sanitization in Drupal 10.2.

The sanitization process is also enhanced by a new sanitization Event and changes to FileUploadResource plugin constructor. 

New UI to hide or show blocks based on HTTP responses

Drupal 10.2 empowers site builders to hide or show website blocks based on the most common HTTP responses: Success (200), Access denied (403), and Page not found (404). There is a new block condition plugin with checkboxes for these responses. It can be found by clicking “Configure” next to a specific block.

The new UI for managing block visibility based on HTTP status codes in Drupal 10.2.The new UI for managing block visibility based on HTTP status codes in Drupal 10.2.

4. New features in Drupal 10.2 for development

Support for PHP 8.3

The newly released Drupal 10.2 supports PHP 8.3 that is bound to boost Drupal developers’ workflows. It is recommended to use at least PHP 8.2 on your server to work with the new version of Drupal.

As usual, the new major release of the programming language brings performance improvements and general cleanup. Among specific new features, PHP 8.3 offers explicit typing of class constants, deep-cloning of readonly properties, dynamic class constant fetch, the new #[\Override] attribute, the new json_validate() function, and additions to the randomizer functionality. 

Using PHP attributes instead of annotations

To provide additional information about the classes, properties, or methods in plugins, Drupal developers rely on Doctrine annotations — special comments that are embedded directly in the code. It’s a feature in the Doctrine ORM (Object-Relational Mapping) library for PHP.

A compelling change in Drupal 10.2 enables developers to use PHP attributes instead of annotations. PHP attributes were introduced in PHP 8.0 to provide native support for the metadata. Attributes became even easier to use PHP 8.1 with the introduction of such features as New in initializers and Readonly properties.

PHP attributes are a native language feature that is not tied to a specific library. They provide a cleaner and more concise syntax compared to annotations. The work is still going on for converting plugin types so they can use attributes. For now, Drupal 10.2 supports this for Action and Block plugins.

A “before and after” example of attributes replacing annotations in Drupal 10.2.A “before and after” example of attributes replacing annotations in Drupal 10.2.

Improved support for validation constraints in configuration forms

Drupal 10.2 offers enhancements in working with validation constraints — rules or conditions that help validate the correctness of the data in configuration settings. To facilitate the use of validation constraints in simple configuration forms, a new #config_target property has been introduced to the ConfigFormBase class in Drupal’s Form API. To date, the new property has been adopted in several Drupal core configuration forms.

Symfony autoconfiguration for event subscriber

Starting with Drupal 10.2, event subscribers have Symfony autoconfiguration enabled. This eliminates unnecessary steps in a developer’s work. All that is needed is to set the autoconfigure: true in the _defaults section of a module’s services.yml file. Then all event subscriber services will be registered automatically, with no need to be individually tagged.

And more for developers

Drupal developers also might appreciate:

Final thoughts

The new features in Drupal 10.2 are bound to help you elevate all workflows related to your Drupal website, both technical and non-technical ones. Keep your website updated to rely on the latest innovations and best practices on the digital landscape.

Dec 26 2023
Dec 26

Violinist.io is well into its seventh year. It has survived a couple of Drupal core major upgrades and remains a profitable SaaS based on Drupal and a vital tool for agencies and organizations. I'm not sure if there is such a thing as social historical code archeology, but if there was, then this blog post might be it. I have long wanted to write some blog posts about the development of violinist.io that not only focused on exciting or interesting technical aspects. Instead I think this will highlight when things do not go as planned, why that was, and maybe something around what it meant at the time comparing to present day?

The project started as a PHP file and proof of concept in Drupal Developer Days Seville 2017. Literally. It was one PHP script placed inside one file. After I got that working I figured I would make it a composer package and run it with Drupal cron. This was the first version of violinist.io. Run the method of a composer package with cron. I made a module to contain this hook_cron implementation. I called it cronner. It was the module to do things on cron so that was the name. The cronner module.

cronner.module was first checked in to git with the revision 55da4a9c:

From 55da4a9c
Date: Thu, 23 Mar 2017 08:38:57 +0100
Subject: [PATCH] This is a site now?

As we may deduce from the commit message, this was a pretty early one in the codebase. More precisely, this was the second commit to the codebase, after some initial scaffolding files. At that point, this was the entire contents of cronner.module:

getStorage('node')
    ->loadByProperties([
      'type' => 'project'
    ]);
  $q = \Drupal::queue('cronner_project');
  foreach ($nodes as $node) {
    $q->createItem($node);
  }
}

Basically a cron implementation to queue all projects on the site periodically.

Fast forward to 2023 and some reflections around maintaining a codebase. Cronner. I can honestly say that cronner is among my least proud namings in the codebase. The hook_cron implementation is long gone and replaced with all sorts of fancy cloud computing. The name really is misleading now. It contains no cron implementations like that any more. Instead it contains (among other things):

  • Schema for the database table called cronner_log
  • Some random theme hooks used here and there
  • A hook_node_insert implementation that is still the one responsible for adding new projects to the update queue

I have many times wanted to work on extracting parts of cronner.module into separate modules. But fixing bugs, improving test coverage or implementing new features has always been the priority. At this point, from time to time I open the file cronner.module and shudder. But after that initial feeling of annoyance or shame I instead focus on the image of something like the origin life form. Something like a web of branches and roots coming out of a mother plant. Something almost alien-like. The module that started it all, the breathing and growing mother of the codebase.

Some might call it legacy. I call it cronner, the origin mother of the codebase.

Dec 26 2023
Dec 26

I bet a lot of you learned about Drupal and the awesome Drupal Community at a local Drupal event. I went to the Western Massachusetts Drupal Camp back in 2011 after I was assigned a Drupal website to support at work and had no idea what Drupal even was. I attended many great sessions, met a lot of folks in the local Drupal community and even purchased the “Definitive Guide to Drupal 7” (remember all the great authors and the almost 1200 pages of Drupal insights). I was hooked on Drupal that day and never looked back.

History

The number of Drupal events increased over the years and sprung up in Drupal communities around the globe as Drupal popularity expanded. The idea of an official event organizers group was discussed by a large group of event organizers at DrupalCon Seattle in 2019 through a roundtable organized by Rachel Lawson (rachel_norfolk ) of the Drupal Association. As discussions ensued there was an enthusiastic response to the idea of forming an official group to support current and future event organizers.

In 2019 a group of event organizers decided to make this official and worked with the Drupal Association to create a charter, propose it to Dries and once accepted, the official “Event Organizers Working Group” was formed.

The mission of the Event Organizers Working Group (EOWG) is to support community-led teams to grow Drupal through local events.

EOWG Board

Current EOWG board members include: 

  • Avi Schwab, President (froboy) - MidCamp, Midwest Open Source Alliance - Chicago, IL, USA
  • Leslie Glynn, Vice President (leslieg)  Design 4 Drupal Boston, NEDCamp - Boston, MA, USA
  • Sean Walsh, Secretary (seantwalsh) - DrupalCamp NJ - Eatontown, NJ, USA
  • Matthew Saunders (MatthewS) - Drupalcamp Colorado - Denver, CO, USA
  • John Picozzi (johnpicozzi) - New England Drupal Camp (NEDCamp) - RI, USA
  • Bert Boerland (bertboerland) - drupaljam.nl  and splashawards.nl  - Netherlands
  • Suchi Garg (gargsuchi) - Drupal Melbourne meetup - Melbourne, Australia

Current Advisory group members:

  • Nico Grienaur (Grienaur) - DrupalAT Board Member, DrupalCamp Vienna, DrupalCon Vienna 2017, Splash Awards AT, Drupal Developer Days 2023, Drupical - Vienna, Austria
  • Kristen Pol (Kristen Pol) - Drupal Contribution - Santa Cruz, CA, USA
  • Salim Lakhani (salimlakhani) - Drupalcamp Colorado, Florida Drupal Camp - Denver, CO, USA

We are currently seeking nominations for folks interested in joining the board or the advisory committee. Please consider nominating yourself or someone you feel would be interested by commenting on this issue.

Meetings

EOWG Board meetings are held on the first Tuesday each month via Zoom. 

General Event Organizer meetings are currently held as asynchronous meetings on the second Tuesday each month in the Event Organizers channel in Drupal Slack. The chat thread is open 24 hours so that folks around the globe can contribute to the chat discussions at a time that works best for them. All are welcome to join the discussions or add new topics to be discussed.

Involvement in meetings and initiatives will be recognized with credits on the Event Organizers issue queue.

EOWG slack screenshotEOWG slack screenshotParticipating in a Slack meeing

Current Initiatives:

  • Event website platform - An easy way to spin up  a website for a new or existing event.
  • Community Events page on Drupal.org. The global events page lists upcoming Drupal Camps, DrupalCons, Local Meetups, Upcoming Trainings, Contribution Events, Calls for Content and Proposed Events. Make sure to add your event information to this page so folks will be able to see all Drupal events in one place. You can include whether the event is in-person, virtual or both. Make sure to include a link to your event website.
  • Contribution events - Contribution events include Global Contribution Days, Contribution days at Drupal Camps, and other events and ways to contribute to the various initiatives. Contributing to Drupal helps make the Drupal experience better for everyone.
  • Marketing of the EOWG and how event organizers can benefit from information shared by the EOWG.

Let us know if you have an interest in working with Board member(s) on any of these initiatives.

EOWG event mapEOWG event mapThe Community Events page

To Dos

  • Add your event to the Events/Community events page on Drupal.org.
  • Sign up for the EOWG Newsletter to get up to date information on upcoming meetings, initiatives, and upcoming events around the globe. Subscribe on the Events - Community Events page on Drupal.org
  • Join the Event Organizers channel on Drupal slack - #event-organizers at drupal.slack.com
  • Consider nominating yourself for an EOWG Board seat 
  • Reach out to any of the Board members with questions or ask in our Slack channel. 

We look forward to collaborating with current event organizers and those considering starting new events. All are welcome to join.

Leslie Glynn is the Manager of Customer Success at Redfin Solutions in Portland, Maine. She is currently an initiative lead for the Project Browser Strategic Initiative and is the Vice-President of the EOWG. She is a former At-large Director on the Drupal Association Board of Directors and was the 2019 recipient of the Aaron Winborn Award. Leslie champions Drupal whenever possible. She helps organize several Drupal events including Design 4 Drupal Boston and New England Drupal Camp (NEDCamp). When she’s not working, Leslie enjoys watching Boston area sports teams and spending time with her family and her granddaughter, Isla.

Dec 26 2023
Dec 26

Nowadays, the stakes are getting higher and higher. Speed is of the essence for an ordinary user, and each web page and developer has to comply with it. This is where Drupal's BigPipe module takes the stage. 
BigPipe is not a simple addition but a powerful tool for ensuring faster page loading times. It is achieved by fragmenting a web page into smaller units and prioritizing their loading order. Following this logic, the most crucial parts will be loaded first, while the others will catch up later. Let's delve deeper into this marvelous technology and learn more about web page optimization using Drupal's BigPipe module

Drupal's BigPipe module for website performance

Your website's speed isn't just a figure; it's the gateway to user engagement, conversions, and even your virtual credibility. Google's Core Web Vitals have decreed that swift websites shall be rewarded with higher search engine rankings. 
Picture this, you click a link, and instead of waiting impatiently for the entire page to load, key elements gracefully appear, inviting you to start exploring. At the same time, the rest falls seamlessly into place. This is the magic of BigPipe – a module that optimizes site performance with surgical precision, crafting an experience that's not just fast but fluid. 

The origin of BigPipe

The origin of BigPipe can be traced back to the corridors of Facebook, where it was conceived and crafted as a potent solution to a universal challenge - slow-loading web pages. It was born by the engineering team, who recognized the pivotal role that page load times play in user experience and engagement.
Traditional methods of web page delivery often resulted in delays, leaving users staring at blank screens or spinning icons. BigPipe was envisioned as a revolutionary approach to tackle this problem head-on. It took inspiration from progressive rendering, a technique that delivers content in stages, prioritizing critical elements first. This dramatically improved perceived page load times and transformed the user experience, creating a sense of immediate interactivity.
Facebook's implementation of BigPipe yielded remarkable results. It significantly reduced page load times, enhanced user engagement, and contributed to a more seamless and enjoyable browsing experience on the platform. Recognizing the potential of this innovation, the concept of BigPipe transcended its origins within Facebook and began to gain traction in the broader web development community.
The concept was soon adapted beyond social media, finding its way into content management systems like Drupal. In Drupal, the BigPipe module was developed to replicate the approach, bringing the benefits of accelerated content delivery to a broader range of websites.

How does the BigPipe module work?

The BigPipe module hinges on a simple yet ingenious concept. It immediately serves the essential parts of a web page, providing users with a swift and engaging experience. At the same time, the rest of the content follows suit in the background.
Here's a breakdown of the BigPipe module's work:

  1. Page Segmentation: Following a web page request, BigPipe breaks that page down into smaller segments - pagelets. Those pagelets mainly consist of the page's distinct sections, such as the header, navigation, content blocks, and footer.
  2. Priority Rendering: Pagelets' categorization is based mainly on their importance for user interaction. Critical pages, like the header and initial content, are designated as high-priority and are loaded to the user's browser immediately.
  3. Initial Delivery: BigPipe's delivery process starts by sending the pagelets with higher priority to the browser, allowing the browser to display them without waiting for the page to load fully.
  4. Progressive Loading: When the user already interacts with the loaded pagelets, BigPipe continues to work behind the scenes. It progressively fetches and sends the remaining pagelets to the browser, thus fully loading the page over time.
  5. Client-Side Rendering: On the arrival of additional pagelets, the browser takes control of page rendering, and the content is positioned in the correct order. This ensures that users feel swift and smooth loading, even if some elements are still coming.
  6. User Interaction: When high-priority content is loaded, users can interact with the page while the remaining content loads.
  7. Content Completion: BigPipe ensures that all pagelets are fetched and displayed in their correct order to render the page correctly. Users enjoy a comprehensive and fully loaded page without experiencing long loading delays.

Useful tips for maximizing BigPipe benefits

Prioritize Critical Content

Identify the most crucial elements of your web pages that must be loaded and visible immediately, such as the head, main navbar, or initial content. Prioritize rendering these elements by configuring BigPipe. This will swiftly enhance perceived performance and user engagement on the site.

Optimize Dynamic Elements

BigPipe excels at promptly serving static content. Beware that dynamic elements can disrupt the user experience. To avoid this, implement efficient coding practices to generate dynamic content quickly. For frequently changing dynamic components, combine BigPipe with server-side caching. This will ensure a seamless and speedy user experience.

Complement with Caching

BigPipe and caching go hand in hand. Leverage Drupal's caching mechanisms, including page and block caching, to reduce load times for returning users. Fine-tune cache settings to align with BigPipe's strategy – static components are loaded quickly while dynamic content remains responsive.

Lazy Load Images

Implement lazy loading for images to prevent resource-intensive images from slowing down initial page loading. With lazy loading, images are only loaded when they come into the user's viewport, reducing the initial load and enhancing the perception of speed.

Optimize Images

Image optimization is a gold mine of performance. Before uploading images to your website:

  1. Resize and compress them.
  2. Utilize modern image formats like WebP for better compression.
  3. Pair image optimization with BigPipe to ensure that even visually rich websites load swiftly.

Minify JavaScript and CSS

Minification is a tried-and-true technique to trim the size of JavaScript and CSS files. Smaller files load faster, contributing to quicker page rendering. Apply minification to your codebase and combine it with BigPipe's incremental rendering to create a zippy browsing experience.

Implement Browser Caching

Leverage browser caching to store static assets locally on users' devices. This means that when users revisit your site, their browsers can load cached assets instead of fetching them anew. This drastically reduces load times and server requests, further enhancing the speed of your site.

Choose a Performant Hosting Provider

Opt for a hosting provider that offers solid infrastructure and optimal server performance. BigPipe's benefits are amplified with a robust hosting environment that ensures quick server response times.

Regularly Audit and Optimize

Website performance is an ongoing endeavor. Periodically audit your site's performance using tools like Google PageSpeed Insights or GTmetrix. Optimize your code, images, and content to align with BigPipe's accelerated delivery approach.

User Testing and Feedback

Test your website's performance and user experience across different devices and network speeds. Gather user feedback to identify areas where further optimization is needed. Adjust BigPipe's configuration and other techniques based on real-world user insights

Top myths about the BigPipe module

Myth: BigPipe is Only Relevant for Large Websites
Truth: Originating from a big platform like Facebook, BigPipe's benefits extend beyond large-scale websites. Content-prioritizing delivery approach is beneficial for websites of all sizes. 

Myth: BigPipe Requires Complex Setup and Configuration
Truth: It may sound complex, but you don't need to be a coding wizard to implement BigPipe into your page.

Myth: BigPipe Solves All Website Performance Issues
Truth: BigPipe is a powerful tool but not a magical solution to eradicate all performance problems. BigPipe optimizes perceived page load times and prioritizes content delivery. Performance factors like server response times, image optimization, and code efficiency also play crucial roles in web page optimization. 

Myth: BigPipe is Incompatible with Dynamic Content
Truth: Some may assume BigPipe's content prioritization clashes with dynamically generated content. It can efficiently handle both static and dynamic elements on a web page. The module's strategy ensures high-priority, static components load swiftly, while active elements, like personalized recommendations or user-specific content, can seamlessly follow suit.

Myth: BigPipe Eliminates the Need for Caching
Truth: Even today, caching is a fundamental aspect of web performance optimization, so BigPipecan'tt replaces it. Instead, these two tools complement each other. BigPipe enhances the initial page rendering, while caching mechanisms, like Drupal's built-in caching system or Content Delivery Networks (CDNs), continue to expedite subsequent visits. 

Myth: BigPipe is Only Effective for First-Time Visitors
Truth: BigPipe's impact extends beyond first-time visitors. While it shines brightest for initial page loads, its progressive loading mechanism benefits returning users too. 

Myth: BigPipe Requires Extensive Development Resources
Truth: Implementing BigPipe doesn't demand a colossal investment of development resources. Drupal'ss BigPipe module simplifies the process, making it accessible to many developers. 

Improve your website performance with BigPipe Module!

User experience, the heart, and soul of online interactions, hinges on site performance. After the user opens your site, every millisecond counts.  BigPipe transforms your website from a static canvas to an interactive playground. Visitors can dive into your content almost instantly, explore, click, and interact without the agony of waiting. Explore the potential of the BigPipe module and infuse your Drupal website with our web development company

Dec 25 2023
Dec 25

Today we are talking about Drupal in 2024, What we are looking forward to with Drupal 11, and the Drupal Advent Calendar with James Shields. We’ll also cover Drupal 10.2 as our module of the week.

For show notes visit:
www.talkingDrupal.com/430

Topics

  • Advent calendar
  • Selection process
  • Popularity
  • Next year
  • Drupal features in 2024
  • Drupal 11
    • Project browser
    • Recipes / Starter templates
    • Automated updates
    • Gitlab
    • Smaller core
  • Predictions

Resources

Guests

James Shields - lostcarpark.com lostcarpark

Hosts

Nic Laflin - nLighteneddevelopment.com nicxvan
John Picozzi - epam.com johnpicozzi
Martin Anderson-Clutz - mandclu
Ron Northcutt - community.appsmith.com rlnorthcutt

MOTW

Correspondent

Martin Anderson-Clutz - mandclu
Drupal 10.2

  • Improvements include
    • Technology Updates
      • PHP 8.3
      • Includes capabilities that previously required contrib projects
      • File name sanitization
      • A search filter on the permissions page
    • End Users
      • Performance enhancements and improved caching APIs
      • Support for PHP Fibers to accelerate handling things like asynchronous remote calls
    • Content Creators
      • Revision UI for media
      • Wider editing area in Claro on large screens
      • The return of “Show blocks” in CKEditor 5, missing until now
    • Site Builders
      • Field creation UI has a new, more visual interface, and an updated workflow
      • Block visibility can now be based on the HTTP response status, for example to make it visible or invisible on 404 or 403 responses
      • Tour module is no longer enabled by default for the Standard and Umami profiles
      • New “negated regular expression” operator for views filters (string/integer), to exclude results matching a provided pattern
    • Site Owners
      • Announcements Feed is now stable and included in the Standard profile
      • The functionality in the experimental Help Topics module has been merged into the main Help module, so the Help Topics module is now deprecated
      • New permission: Use help pages
    • Developers
      • A fairly sizable change is a move to use native PHP attributes instead of doctrine annotations to declare metadata for plugin classes. Work is already underway to get core code converted, and an issue has been opened to have rector do this conversion for contrib projects
      • A new DeprecationHelper::backwardsCompatibleCall() method to help write Drupal extensions that support multiple versions of core
      • A PerformanceTestBase is now in core, to support automated testing of performance metrics
      • A new #config_target property in ConfigFormBase to simplify creating configuration forms
      • Symfony mailer is now a composer dependency of core
      • New decimal primitive data type
      • Expanded configuration validation, Symfony autowiring support, HTML5 output from the HTML utility class is now default, and more
      • In addition to these and the features highlighted in the official announcement, there are three pages of change records for the 10.2.0 release, and we’ll include a link to those in the show notes
Dec 24 2023
Dec 24

Routes in Drupal can be altered as they are created, or even changed on the fly as the page request is being processed.

In addition to a routing system, Drupal has a path alias system where internal routes like "/node/123" can be given SEO friendly paths like "/about-us". When the user visits the site at "/about-us" the path will be internally re-written to allow Drupal to serve the correct page. Modules like Pathauto will automatically generate the SEO friendly paths using information from the item of content; without the user having to remember to enter it themselves.

This mechanism is made possible thanks to an internal Drupal service called "path processing". When Drupal receives a request it will pass the path through one or more path processors to allow them to change it to another path (which might be an internal route). The process is reversed when generating a link to the page, which allows the path processors to reverse the process.

It is possible to alter a route in Drupal using a route subscriber, but using path processors allows us to change or mask the route or path of a page in a Drupal site without actually changing the internal route itself.

In this article we will look what types path processors are available, how to create your own, what sort of uses they have in a Drupal site, and anything else you should look out for when creating path processors.

Types Of Path Processor

Path processors are managed by the Drupal class \Drupal\Core\PathProcessor\PathProcessorManager. When you add your a path processor to a site this is the class that manages the processor order and calling the processors.

There are two types of path processor available in Drupal:

  • Inbound - Processes an inbound path and allows it to be altered in some way before being processed by Drupal. This usually occurs when a user sends a request to the Drupal site to visit a page. Inbound path processors can also be triggered by certain internal processes, for example, when using a path validator. The path validator will pass the path to the inbound path processor in order to change it to ensure that it has been processed correctly.
  • Outbound - An outbound path is any path that Drupal generates a URL. The outbound path processor will be called in order to change the path so that the URL can be generated correct.

Basically, the inbound processor is used when responding to a path, the outbound processor is called when rendering a path.

Let's go through a couple of examples of each to show how they work.

Creating An Inbound Processor

To register an inbound service with Drupal you need to create a service with a tag of path_processor_inbound, and can optionally include a priority. This let's Drupal know that this service must be used when processing inbound paths.

It is normal for path processor classes to be kept in the "PathProcessor" directory in your custom module's "src" directory.

services:
  mymodule.path_processor_inbound:
    class: Drupal\mymodule\PathProcessor\InboundPathProcessor
    tags:
      - { name: path_processor_inbound, priority: 20 }

The priority you assign to the path_processor_inbound tag will depend on your setup. The internal inbound processor that handles paths in Drupal has a priority of 100, so any setting less than 100 will cause the processing to be performed before Drupal's internal handler is called.

The InboundPathProcessor class we create must implement the \Drupal\Core\PathProcessor\InboundPathProcessorInterface interface, which requires a single method called processInbound() to be added to the class. Here are the arguments for that method.

  • $path - This is a string for the path that is being processed, with a leading slash.
  • $request - In addition to the path, the request object is also passed to the method. This allows us to perform any additional checks on query strings on the URL or other parameters that may have been added to the request.

The processInbound() method must return the processed path as a string (with the leading slash). If we don't want to alter the path then we need to return the path that was passed to the method.

To create a simple example let's make sure that when a user visits the path at "/some-random-path" that we translate this internally to be "/node/1", which is not the internal route for this page. In this example, if the path passed into the method isn't our required path then we just return it, effectively ignoring any path but the one we are looking for.

Now, when the user visits the path "/some-random-path" they will see the output of the page at "/node/1". It is still possible to view the page at "/node/1/" and see the output, so we have just created a duplicate path for the same page.

This is a simple example to show how the processInbound() method works, we'll look at a more concrete example later.

Creating An Outbound Processor

The outbound processor is defined in a similar way to the inbound processor, but in this case we tag the service with the tag path_processor_outbound.

services:
  mymodule.path_processor_outbound:
    class: Drupal\mymodule\PathProcessor\OutboundPathProcessor
    tags:
      - { name: path_processor_outbound, priority: 250 }

The priory of the path_processor_outbound is more or less the opposite of the inbound processor in that you'll generally want your outbound processing to happen later in the callstack. The internal Drupal mechanisms for outbound processor is set at 200, so setting our priory to 250 means that we process our outbound links after Drupal has created any aliases.

The OutboundPathProcessor class we create must implement the \Drupal\Core\PathProcessor\OutboundPathProcessorInterface interface, which requires a single method called processOutbound() to be added to the class. Here are the arguments for that method.

  • $path - This is a string for the path that is being processed, with a leading slash.
  • $options - An associative array of additional options, which includes things like "query", "fragment", "absolute", and "language". These are the same options that get sent to the URL class when generating URLs and allow us to update the outbound path based on the passed options.
  • $request - The current request object is also sent to the method and can make decisions based on the parameters passed to the current path.
  • $bubbleable_metadata - An optional object to collect path processors' bubbleable metadata so that we can potentially pass cache information upstream.

The processOutbound() method must return the new path, with a starting slash. If we don't want to change the path then we just return the path that was sent to us, otherwise we can make any change we require and return this string.

Taking a simple example in the inbound processor further, let's change the path "/node/1" to be "/some-random-path". In this example we are looking for the internal path of "/node/1", and if we see this path then we return our new path.

With this in place, when Drupal prints out a link to "/node/1" it will render the path as "/some-random-path".

On its own this example doesn't do much; we are just rewriting a path for a single page. The real power is when we combine inbound processing and outbound processing together. Let's do just that.

Creating A Single Class For Path Processing

It is possible to combine the inbound and outbound processors together into a single class by combining the tags in a single service. This can be done by combining the path processors together in the module's services file.

services:
  mymodule.path_processor:
    class: Drupal\mymodule\PathProcessor\MyModulePathProcessor
    tags:
      - { name: path_processor_inbound, priority: 20 }
      - { name: path_processor_outbound, priority: 250 }

The class we create from this definition implements both the InboundPathProcessorInterface and the OutboundPathProcessorInterface, and as such it includes both of the processInbound() and processOutbound() methods.

Now all you need to do is add in your path processing.

It's a good idea to create a construct like this so that you translate the path going into and coming out of Drupal. This creates a consistent path model and prevents duplicate content issues where different pages have the same path.

The Redirect Module

If you are planning to use the inbound path processor system then you should be aware that the Redirect module will attempt to redirect your inbound path processor changes to the rewritten paths. The Redirect module is a great module, and I install it on every Drupal site I run, but in order to prevent this redirect you'll need to do something extra, which we'll go through in this section.

To prevent the Redirect module from redirecting a path you need to add the attribute _disable_route_normalizer to the route before the kernel.request event triggers in the Redirect module's RouteNormalizerRequestSubscriber class. We do this by creating our own event subscriber and giving it a higher priority.

The first thing to do is add our event subscriber to our custom module services.yml file.

  mymodule.prevent_redirect_subscriber:
    class: Drupal\mymodule\EventSubscriber\PreventRedirectSubscriber
    tags:
      - { name: event_subscriber }

The event subscriber itself just needs to listen to the kernel.request event, which is stored in the KernelEvents::REQUEST constant. We need to trigger our custom module before the redirect module event, and so we set the priority of the event to be 40. This is higher than the Redirect module event, which is set at 30.

All the event subscriber needs to do is listen for our path and then set the _disable_route_normalizer attribute to the route if it is detected.

getRequest()->getPathInfo() === '/en/some-random-path') {
      $event->getRequest()->attributes->set('_disable_route_normalizer', true);
    }
  }

}

When the Redirect module event triggers it will see this attribute and ignore the redirect.

This will only happen if you are changing the path of an entity of some kind using only the inbound path processor. Creating only the inbound processor creates an imbalance between the outer path and the translated inner path, which we then need to let the Redirect module know about to prevent the redirect. If we also translated the outbound path in the same (and opposite) way then the redirect wouldn't occur.

Doing Something Useful

We've looked at swapping paths and preventing redirects, but let's do something useful with this system.

I was recently tasked with creating a module that would allow any page to be rendered as RSS. It wasn't that we needed a RSS feed, but that each individual page should have an RSS version available.

This was required as there was an integration with an external system that was used to pull information out of a Drupal site for newsletters. Having RSS versions of pages made it much easier for the system to parse the content of the page and so produce the newsletter. This also meant that if the theme changed the system wouldn't be effected as it wouldn't be using the theme of the site.

Essentially, the requirement meant that we needed to add "/rss" after any page on the site and it would render the page accordingly.

The resulting module was dubbed "Node RSS" and made extensive use of path processors to produce the result.

The first step was to create a controller that would react to path like "/node/123/rss" to render the page as an RSS feed. This required a simple route being set up to allow Drupal to listen to that path and also to inject the current node object into the controller. The route also contains a simple permission, which provided a convenient way of activating the system when it was ready.

node_rss.view:
  path: '/node/{node}/rss'
  defaults:
    _title: 'RSS'
    _controller: '\Drupal\node_rss\Controller\NodeRssController::rssView'
  requirements:
    _permission: 'node.view all rss feeds'
    node: \d+
  options:
    parameters:
      node:
        type: entity:node

The rssView action of the NodeRssController just needs to render the node and return it as part of an RSS document. Using this we can now go to a node page at "/node/123/rss" and see an RSS version of the page.

I won't go into detail about producing the RSS version of the page here as it contains a lot of boilerplate code that goes beyond the scope of this article.

So far we only have half the functionality required. Seeing an RSS version of the page via the node ID is fine, but what we really want is to visit the full path of the page with "/rss" appended to the end.

The next step is to setup our path processor so that we can change the paths on the fly. In addition to the tags we are also passing in two other services for us to use in the class. These services are the path_alias.manager service for translating paths and the language_manager to ensure that we get the path with the correct language.

services:
  node_rss.path_processor:
    class: Drupal\node_rss\PathProcessor\NodeRssPathProcessor
    arguments:
      - '@path_alias.manager'
      - '@language_manager'
    tags:
      - { name: path_processor_inbound, priority: 20 }
      - { name: path_processor_outbound, priority: 220 }

The processInbound() method looks for the "/rss" string at the end of the passed path. If this is found then we remove that from the path and try to find the internal path of the page in the site. If we do find the path then it will be returned as "/node/123" instead of the full path alias and this means we can just append "/rss" to the end of the path to point the path at our NodeRssController::rssView action.

  public function processInbound($path, Request $request): string {
    if (preg_match('/\/rss$/', $path) === 0) {
      // String is not an RSS feed string.
      return $path;
    }

    $nonRssPath = str_replace('/rss', '', $path);
    $internalPath = $this->pathAliasManager->getPathByAlias($nonRssPath, $this->languageManager->getCurrentLanguage()->getId());

    if ($internalPath === $nonRssPath && preg_match('/^node\//', $internalPath) === 0) {
      // No matching path was found, or, it wasn't a node path that we have.
      return $path;
    }

    return $internalPath . '/rss';
  }

The opposite process needs to happen for the processOutbound() method. In this case we look for a path that looks like "/node/123/rss" and convert this back into the full path alias of the page. If we find an alias for that path then we append "/rss" to the path and return it.

  public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL): string {
    if (preg_match('/^\/node\/.*?\/rss?$/', $path) === 0) {
      // String is not an RSS feed string.
      return $path;
    }

    $nonRssPath = str_replace('/rss', '', $path);
    $alias = $this->pathAliasManager->getAliasByPath($nonRssPath, $this->languageManager->getCurrentLanguage()->getId());

    if ($nonRssPath === $alias) {
      // An internal alias was not found.
      return $path;
    }

    return $alias . '/rss';
  }

We now have an RSS feed for any content path on the website (as long as it is a node page of some kind).

If we attempted to visit the RSS output of any other kind of page (like a taxonomy term) then we would receive a 404 error. This is possible thanks to the route we have in place as the parameter will only accept node paths.

As we have translated the path completely we do not need the Redirect module overrides here since there is a coherent input/output mechanism for these paths. It's only when there is an imbalance in the paths that we need to override the Redirect module to prevent redirects.

Don't worry if you are looking for the full source code for the above module as I have recently released the Node RSS module on Drupal.org. It only has a dev release for the time being as I would like to add the ability to pick what content types are available for the feeds. I'm also testing it with different setups to make sure that the feed works in different situations. Let me know if it is useful for you and please create a ticket if you have any issues.

If you want to see another module that makes use of this technique then there is the Dynamic Path Rewrites module. This allows the rewriting of any content path on the fly without creating path aliases. This is an alternative to using modules like Path Auto without actually creating path aliases within your system and uses a nice caching system to speed up the responses.

Conclusion

The path processing system in Drupal is really quite powerful and can be used to build some interesting features that rewrite paths on the fly. We can take any incoming request and redirect it to any path we like on the fly.

Without this system in place we would need to generate additional aliases for every path we wanted and add them to the database before we would be able to use the system. That is fine on smaller sites, but I manage sites with millions of nodes and that amount of data would bloat the database and probably not be used all that much.

Path processing does have some interactions with other modules (like Redirect) but these problems are easily overcome. Perhaps the most complex part of this is ensuring that you have the right weights to some of the interactions here as getting things wrong will likely lead to unwanted interactions.

Dec 22 2023
Dec 22

Authored by: Nadiia Nykolaichuk.

Websites are complex systems, and navigating them with assistive tools like screen readers is not an easy task. Luckily, if a website follows the accessibility guidelines, the user journey becomes much more seamless thanks to useful “road signs.” They are contained in the HTML code of accessible web pages and announced to the user by screen readers.

Among the elements that are specifically good for this mission are semantic HTML and WAI-ARIA. So they will be the next “heroes” of our series about essential accessibility elements after the previously featured alt text for images. Of course, the role of a CMS in supporting web accessibility is huge, so after the general overview of semantic HTML and WAI-ARIA, we’ll focus on how they work in Drupal.

Semantic HTML and WAI-ARIA for accessibility

Semantic HTML and WAI-ARIA complement each other in ensuring that web content communicates its meaning, purpose, and structure in a clear and meaningful way. For better clarity, it needs to be noted that “content” in the context of web accessibility is used as a broad term that encompasses everything that a web page conveys. This includes not only the main text or images, traditionally thought of as content, but also various elements and components that shape the user interface. So let’s take a closer look at what semantic HTML and WAI-ARIA do specifically for improving web accessibility.

The key overview of semantic HTML

Semantic HTML is the use of appropriate HTML (Hypertext Markup Language) tags in web pages so they can be properly understood and processed by various technologies — parsed by browsers, crawled by search engines and, of course, understood by screen readers, which is paramount for web accessibility.

It needs to be noted that utilizing semantic HTML to improve the accessibility of web pages is a mission for both developers and content creators. While developers rely on semantic HTML to define the larger structure of the web page including all its UI elements, content creators take care of creating accessible content. WYSIWYG (What You See Is What You Get) editors inside CMSs enable them to do it with no actual knowledge of HTML. They can use its UI to organize content hierarchically with heading tags (

,

. etc.) or create other elements, such as blockquotes, bullet lists, tables, and more.

Even the name of each semantic HTML tag communicates the purpose of the element. For example, here are some tags introduced in the latest version of the Hypertext Markup Language — HTML5 — that provide more meaningful and specific labels for various parts of a web page:

  • (for the top section of a web page)
  • (for the navigation section of a web page that contains links to other parts of the site)
  • (for the main content of the page)
  • (for a specific topic or area of the page)
  • (for a self-contained piece of content)
  • (the bottom section of a web page)
An example of a code snippet with semantic HTMLAn example of a code snippet with semantic HTML.

The arrival of new semantic elements marks a shift away from the practices of using generic, non-semantic tags like

or for any type of information. And while and are still in use on websites, it's recommended to prioritize the use of the specific HTML5 semantic elements for website accessibility and the clarity of code.

The key overview of WAI-ARIA

WAI-ARIA (the acronym for “Web Accessibility Initiative — Accessible Rich Internet Applications”) is a set of guidelines and tools provided by the World Wide Web Consortium (W3C). The latest version of the specification is WAI-ARIA 1.2 published in June 2023.

WAI-ARIA enables developers to add attributes (roles, properties and states) to HTML elements to enhance their semantics. This provides additional information to assistive technologies that is related to dynamic changes, the current states of the elements, user interaction features, etc. This makes ARIA especially useful for websites with dynamic content or advanced user interface controls.

ARIA provides information that is not conveyed by native HTML elements alone. However, when native HTML tags are enough, using WAI-ARIA is not recommended to avoid overlaps. 

WAI-ARIA attributes include:

  • ARIA roles. They define the type and purpose of various web page elements (a button, a navigation menu, a form input field, a progress bar, etc.). For example, ‘role=“progressbar”’.
  • ARIA states. They communicate the information about the current state of an element to assistive technologies. For example, the ‘aria-checked’ state can be used to tell that a checkbox or a radio button is checked (‘aria-checked=“true”’) or unchecked (‘aria-checked=“false”’). Another example is the ‘aria-selected’ state can indicate that an option within a set of options is currently selected.
  • ARIA properties. They provide additional information about the characteristics of the element. For example, the ‘aria-hidden’ property followed by the “true” or “false” value indicates whether the element should be ignored by screen readers or not (they can ignore it when it’s decorative, redundant, or not relevant for the current context). Another example is the ‘aria-valuemin’, ‘aria-valuemax’, and ‘aria-valuenow’ properties that define the minimum, maximum, and current value of a widget like a progress bar.
An example of a code snippet with WAI-ARIA for a role and properties.An example of a code snippet with WAI-ARIA for a role and properties.

ARIA also categorizes roles into abstract, widget, document structure, and landmark roles. Here are some examples of the key ARIA roles:

  • ARIA widgets. They are interactive components of a web page. Examples of widget roles include ‘role=“button”’, ‘role=“slider”’, ‘role=“tablist”’, etc. An example of a widget role together with a state would be ‘role=“switch” aria-pressed=“false”’ (for a toggle switch that is currently not pressed). 
An example of a code snippet with WAI-ARIA for a widget role with properties and states.An example of a code snippet with WAI-ARIA for a widget role with properties and states.
  • ARIA landmarks. They are a set of roles that define important sections or regions of a web page and help users quickly jump to them. Examples include ‘role=“main”’ (for the main content), ‘role=“navigation”’ (for the main navigation menu), etc. ARIA landmark roles shouldn’t be used over semantic HTML elements (such as , , etc.) to represent landmarks. They are only needed in cases when semantic HTML couldn’t be implemented or you need to provide more explicit information about the purpose of a particular section.

ARIA live regions

Another super important concept in ARIA are live regions — areas on a web page that may be updated dynamically. A live region can be an error message, a message in the chat window, etc. WAI-ARIA attributes help alert assistive technologies about the changes in live regions. The attribute associated with this is ‘aria-live’, which specifies how urgently the screen reader should announce the changes to the user:

  • The ‘aria-live=“polite”’ value means the changes to the live region don’t have to be announced immediately and the user can finish their current task before hearing the alert.
  • The ‘aria-live=“assertive”’ value indicates that changes are so important that they should be announced immediately, interrupting the user's current task.

Semantic HTML and WAI-ARIA in Drupal

The Drupal community is deeply committed to enhancing the accessibility of websites. Currently, the CMS is aspiring to attain the AA accessibility level of Web Content Accessibility Guidelines (WCAG) 2.1. Drupal has been making huge strides in the accessibility area during the last decade, but the arrival of Drupal 8.0 in 2015 marked an especially outstanding leap. Among a bunch of other remarkable accessibility improvements, it added extensive support for semantic HTML5 and WAI-ARIA, bringing Drupal code in line with the up-to-date web development practices. 

“One of the big enhancements that will be evident is a broad adoption of HTML5 and Web Accessibility Initiative's Accessible Rich Internet Applications Suite (WAI ARIA). Neither standard was mature enough to bring into Drupal 7, but we are happy to see their adoption in Drupal 8.”

Mike Gifford, Drupal core committer, “godfather of Drupal accessibility”

The adoption of HTML5 gave developers a chance to create templates and layouts using semantic HTML elements, which helped make content better-structured and more accessible. In his drupal.org article, Mike Gifford explained that much of Drupal’s HTML was brought up to current HTML5 standards with the adoption of the TWIG template engine in Drupal 8.0. Indeed, Twig is renowned for being modern and flexible, so its arrival made Drupal templates more adjusted to leveraging the best HTML5 features for better semantic markup. Within Drupal templates, regions were defined to organize content into meaningful sections.

Thanks to the HTML5 Initiative for Drupal, the template and theme functions were refactored to use new HTML5 elements such as

, , , , , , and so on. Special attention was given to helping Drupal’s Form API adopt a bunch of new HTML 5 form elements that enhance form accessibility. 

When it comes to WAI-ARIA, Drupal became “equipped” with built-in support for its attributes, enabling developers to assign appropriate roles, states, and properties to interactive elements and dynamic content elements like menus, sliders, modals, and so on. The Drupal core and contributed modules were updated to better align with the latest ARIA best practices.

Mike Gifford’s article summed up the key innovations that arrived in Drupal 8.0 to help Drupal developers harness the power of WAI-ARIA:

  • ARIA landmarks were added to Drupal’s core themes.
  • ARIA roles were introduced to the core blocks and status messages.
  • Proactive error notification for users was added through the aria-invalid attribute to alert the users of screen readers whenever there is a problem.
  • The ability to provide additional context for tables was added thanks to the aria-sort attribute. The use of the aria-sort attribute was enhanced in Drupal Views to make tables more accessible. It also gave developers the ability to add caption and summary in tables and include the header for better semantics.
  • The aria-describedby attribute was added to form elements when they had related descriptions, enhancing the accessibility of form fields.

ARIA live regions. Furthermore, developers became able to implement ARIA live regions to dynamically announce the changes so they can be read by screen readers. This is especially useful for applications with real-time updates or notifications. Mike Gifford called it one of the biggest steps for ARIA that Drupal became equipped with the ARIA Live Announcements API, which consolidated all previous implementations to provide super consistent and seamless aural experience to users.

Drupal’s accessibility advances never stop, so the support for HTML5 and ARIA has been improving and extending all the time. The latest major Drupal core version, Drupal 10, has super accessible default themes — the administration theme Claro and the front-end theme Olivero — that also rely on these elements. 

Here is an example of ARIA use in the Claro theme for a live region. There is an announcement that screen readers must read out to the user — “Tray ‘Administration menu’ opened.” A couple of attributes are used to implement this:

  • The ‘aria-live=‘polite’ attribute value tells screen readers that the announcement is not critical and can be made in a “polite” manner — without interrupting any current task.
  • The ‘aria-busy=‘false’ attribute value indicates that the live region is not currently undergoing changes or updates, so there’s no need for screen readers to wait for any modifications to complete before they can read the announcement.
An example of ARIA in the Drupal core admin theme Claro for announcements in a live regionAn example of ARIA in the Drupal core admin theme Claro for announcements in a live region.

 Another example in the Claro theme is related to pressing the main Drupal administration menu link, so let’s check some of its key ARIA attributes:

  • ‘Role=”button”’ assigns the ARIA role of “button” to the admin menu link, indicating that it has a button-like behavior.
  • ‘Aria-pressed=”false”’ is a state indicating that this admin menu link is currently not pressed.
An example of ARIA in the Drupal core admin theme Claro for indicating the pressed/unpressed state of an element.An example of ARIA in the Drupal core admin theme Claro for indicating the pressed/unpressed state of an element.

In his article “The Accessibility And Usability Journey Of Drupal’s Primary Navigation,” Mike Gifford demonstrated the use of ARIA in Drupal’s default front-end theme Olivero. There is the ‘aria-labelledby’ attribute that accompanies the standard

element at the beginning of the menu. The attribute points to an h2 element that is visually hidden but accessible to screen readers. Thanks to this, screen reader users can know the navigation menu name and differentiate the different navigation elements. This also helps users find this menu if they navigate by headings.

For the top-level navigation item, Drupal can use a element injected after the hyperlink, explains Mike. The button contains the ‘aria-controls’ attribute (mapped to the ID of the nested

    ) and the ‘aria-expanded’ attribute. They are initialized with JS, but in case a website is loaded without JS, the button serves for presentational purposes only. The button has visually hidden text with the menu item’s text, followed by “sub-navigation,” explains Mike. This helps people who tab between form controls understand what submenu this button controls. A demo of the Olivero theme’s main navigation with ARIA by Mike Gifford.A demo of the Olivero theme’s main navigation with ARIA by Mike Gifford.

    Some useful contributed Drupal modules for semantic HTML and ARIA

    Semantic Views

    The Semantic Views module enables developers to specify various HTML tags and class attributes inside the Views UI with no need to override the template files. It adds a special Semantic Views Style format, as well as an option to show Semantic Views Row. 

    New format and display options in Views provided by the Semantic Views Row module.New format and display options in Views provided by the Semantic Views Row module.Row style options in Views provided by the Semantic Views Row module.Row style options in Views provided by the Semantic Views Row module.

    Block ARIA Landmark Roles

    The Block ARIA Landmark Role module adds a new option on the block configuration form that enables users to assign an ARIA landmark role to a block.

    Assigning an ARIA landmark role to a Drupal block with the help of the Block ARIA Landmark Role module.Assigning an ARIA landmark role to a Drupal block with the help of the Block ARIA Landmark Role module.

    Final thoughts

    It’s amazing how small tags added to the source code of web pages can make the life of people with disabilities so much easier. With proper use of semantic HTML, WAI-ARIA, and other essential accessibility elements supported by advanced CMSs like Drupal, the World Wide Web can become a genuinely inclusive place. Stay tuned for the next parts of our accessibility series, and remember you can always rely on our Drupal development specialists in taking your website accessibility to the next level. 

Learn from us

Sign up and receive our monthly insights directly in your inbox!

You might also like
Dec 22 2023
Dec 22

We’ve had 35 sessions submitted since we launched the open call and we’d love to add your idea to that number. We’re looking for talks geared toward a variety of attendees, beginner through advanced Drupal users, as well as end users and business owners. Please see our session tracks page for full descriptions of the kinds of talks we are looking for.

Important Dates:

  • Proposal Deadline: January 7 (Sunday), 2024 at midnight CST
  • Tickets on sale: very soon!
  • Early-bird deadline and speakers announced: February (all speakers are eligible for a free ticket, and anyone who submits a session that is not accepted will be eligible for early-bird pricing even after it closes)

Sponsors Get Early, Exclusive Access

Sponsors make MidCamp possible so we want to return the favor by helping your organization grow within the community and save you time.  Get access to new talent and customers as a sponsor of MidCamp.

With packages starting at just $600, organizations can target their jobs to a select group of experienced Drupal talent, maximize exposure by sharing space with dozens of jobs instead of millions, and have three days of being face-to-face with applicants.

Our sponsorship packages are designed to showcase your organization as a supporter of the Drupal community and provide opportunities to:

  • grow your brand,
  • generate leads,
  • and recruit Drupal talent.

Check out the sponsorship packages here, we look forward to working with you to get your organization involved for 2024!

Stay In The Loop

Please feel free to ask on the MidCamp Slack and come hang out with the community online. We will be making announcements there from time to time. We’re also on Twitter and Mastodon.

Keep your eyes peeled in the new year, we will be sharing more information with venue details, hotel and travel options, fun social events, speaker announcements, and more!

Thanks!

The MidCamp Team

Dec 21 2023
Dec 21

MidCamp is Back!

Each year MidCamp brings together people who use, develop, design, and support the Web's leading content management platform, Drupal.

MidCamp will be back in March 2024, and we're reaching out beyond our usual scope of attendees and sponsors. We'd love to have your organization as a 2024 sponsor!

There are many sponsorship levels for 2024 starting at just $600, with the top two tiers limited to just 6 spots overall, and an exclusive offer to save $300 for Supporting level sponsors who sign up before Jan 1st, 2024.

Find the Right Sponsorship Package for You

Key Information

Our sponsorship packages are designed to showcase your organization as a supporter of the Drupal community and provide opportunities to:

  • grow your brand,
  • generate leads,
  • and recruit Drupal talent.

Check out the sponsorship packages here, we look forward to working with you to get your organization involved for 2024

Call for Speakers

Session submissions are open until December 22nd, if sponsorship isn’t an option for your team this year, speaking is a great way to get the word out for your company.

Session Submission Requirements

Stay In The Loop

Join the MidCamp Slack and come hang out with the community online. We will be making announcements there from time to time. We’re also on Twitter and Mastodon.

Keep an eye on this space, we will be releasing more blog posts with venue details, hotel and travel options, fun social events, speaker announcements, and more!

We can’t wait to see you soon! Don’t forget, cancel all those other plans and make MidCamp the only thing happening on your calendar from March 22-24, 2024.

Dec 21 2023
Dec 21

Website migration is an important topic that we've already covered from a broader perspective in another article. In this blog post, we’ll focus on a specific example. We’ll look at the migration of WordPress, the most popular content management system, to Drupal. We’ll examine the benefits of such a migration, how it works, and how we should prepare for it.

Why migrate WordPress site to Drupal?

Migrating WordPress site to Drupal can be beneficial in many situations for the sake of the benefits of the CMS with a blue drop in the logo. With more advanced configuration capabilities, Drupal allows for more complete control over content structure and functionalities, which is especially useful for projects requiring specialized solutions. Here are some benefits for which we should consider such a WordPress migration.

An extensive ecosystem of components

Based on Symfony, Drupal enables us to create powerful features in a simple, fast, and clear way. Thanks to full objectivity, imposed design patterns, and the use of components from this PHP framework, project development and maintenance are easier. A rich ecosystem of ready-made solutions drives efficiency and saves time.

In addition, Drupal gives us many contributed modules created by the community. Even for unusual business needs, there is a good chance that developers centered around this technology have already created a module solution. The Drupal community also deserves praise for its willingness to provide support and share knowledge. Patches for such custom needs are sometimes made for modules, which are introduced with updates over time.

Drupal gathers around an active community of developers who create practical modules for common use.

Source: Drupal.org

Security and access control

If security is a crucial component of the project, Drupal can be a reliable and proven option with better options for managing data access and frequent security updates. Access control for sensitive data is provided by default in Drupal, which is essential for websites storing sensitive information.

In addition, when creating routings or views, we can define precisely who will have access to them. Everything from entire pages and functionalities to individual fields or values that can be selected in a field, we can control with appropriate roles and permissions from the UI.

The advantage of migrating WordPress to Drupal is that you can define user roles and permissions.Drupal allows you to set precise roles on CMS, such as anonymous user, administrator, and editor.

Performance and scalability

Drupal, especially in its latest versions, is optimized for performance and can effectively handle large amounts of content and high traffic. When we plan to expand our website and need scalability, Drupal can handle the growing load better than WordPress.

This system also works well as a backend API. Thanks to built-in functions, we can easily share all the data by passing it in JSON/XML form without going into writing custom logic. This opens up a wide range of possibilities for integration with mobile apps, frontend frameworks, and other tools.

Content management for large websites

For projects with lots of content and complex structures, Drupal offers advanced content management tools. Ready-made entities, such as content types or groups, allow us to logically organize content on our website. The ease and flexibility of categorizing is one of Drupal's main advantages. 

We get not only the ability to act on entities but also to shape them freely. Inserting additional fields, creating relationships between them, and different display options depending on where they are used are just some of the benefits. For example, a view that lists a specific type of content for us, along with advanced filtering and sorting, is clickable in the UI in just a few moments without touching the code.

Customizable administrative interface

If customization of the administrative interface to meet specific needs is essential, Drupal allows advanced modifications and customization. This will be extremely helpful for projects requiring a detailed layout of the admin panel, a specific look, or permissions.

We can add new blocks with valuable links or statistics, create new views that list specific entities for us based on our own conditions, and then modify the links by adding such a view to an existing or new menu. We can also personalize the look of the admin panel using already ready-made skins, as well as our own styles.

Drupal enables you to customize the appearance of the administration panel in the CMS system.


There is also an option to add shortcuts for quick access to frequently used functions.

Another essential advantage is access control, which enables us to precisely manage user permissions to the admin panel. For example, if we don't want a content editor to have access to API integration settings or change Cron jobs.

WordPress to Drupal migration – how does the process work?

We’ve prepared some key steps for migrating from WordPress to Drupal based on our developers' experience. While the specifics of websites may vary, the migration process is usually the same and acts similarly. There is a certain universality in the migration approach, making it more predictable and schematic for most projects.

Preparing for WordPress migration

Before we start the migration, we need to consider in what form we’ll provide the data from the previous website. In most cases, we’ll need an XML file for this, which is generated from WordPress as a backup.

Our first step is to precisely determine what we’ll migrate and where. We’ll try to review the previous website to ensure that what we’re moving is necessary. Let's remember that less sometimes means more. For example, we may no longer need news from 2009 on our new web page. We wrote more about the planning stage in our previous article on website migration.

Configuring Drupal before you migrate WordPress

On the target environment, we prepare the data structure where we’ll store the migrated data. After prior planning, we should know which content to use taxonomies or groups for and which content will need custom entities.

We want this website to be ready before we start the actual migration. It’s also necessary to install the Migrate Plus and CTools modules and WordPress Migrate. It's a good idea, too, to implement the planned modules that will be needed for the target website. Examples include Datetime or Telephone, which will add a corresponding widget for the text field. This will definitely come in handy during data migration.

WordPress content migration

We always start data migration by gathering information. Data is moved from one source to another, and our job is to arrange it into appropriate entities.

In the case of WordPress, it’ll be best to use an XML file exported from the old website for this. Here we can use the previously mentioned WP Migrate module, which will do some work for us. It’ll move categories, blog posts, or content pages. Anything this module doesn’t do automatically, we’ll have to migrate manually by writing our migrations using YML files and migration plugins.

Migrating a website to Drupal consists of three stages:

  • data download,
  • data processing,
  • data storage.

In the first one, we need to retrieve data from a source, such as an SQL database or an XML file in its complete form. For example, it could be a blog article with a title, content, author, graphic information, etc. In the next step, we "tell Drupal" what message should be passed to which field.

This is also an excellent time to organize the website during the migration. We often find that it has a lot of unnecessary data that we don't need to move. These can be omitted altogether or changed during the process and adapted to our new needs. At the last stage of data storage, we create an entry in Drupal as a specific entity.

Notably, the content migration can be divided into many smaller migrations. Instead of one extensive process, we can migrate only users first, then taxonomies and images, and only at the end - specific content types and possibly custom entities.

This approach gives us the ability to easily create relationships between entities already during the migration, since other migration processes can be referenced while the data is being processed. At Droptica, we prefer this form of website migration because we don't have to worry about losing data, and the process becomes more organized.

Website owners sometimes also care about changing the URL structure for given entities. In this case, appropriate redirects are necessary to protect the website's SEO interests. It’s worthwhile already when performing the migration to ensure that the Pathauto module has the required schemes set up.

WordPress migration testing

Once we have the data ready, it’ll be good practice to test it thoroughly. It's worth spending some time on automated tests to thoroughly check all the content. If it turns out that the data doesn't match in terms of content or quantity, that's a sign for us to make the necessary adjustments to the migration plugins.

Using the right content gives us a significant advantage. If we used test content, we might not notice substantial problems. Unexpected errors may appear on user content, which is better solved at this stage. Eliminating edge cases only during development is unfortunately not always possible.

When we also had our own entity in WordPress and had to change or process this data, special attention should be paid to testing such content.

Migrating functionalities from WordPress to Drupal

When it comes to functionality migration, we need to lean for a while longer on the business logic layout. In the case of WordPress, we can even have individual PHP files written inside the CMS.

The modules/plugins provided by the community are also completely different. It's often the case that a problem that in the old system required several plugins (often paid) and additional custom code can be easily "fixed" in Drupal in the form of a contributed module or functionality that is already in the core of the CMS.

At this stage, we have to decide which way to go. We write custom modules, configure or change those provided by the community, and constantly check with the previous website to make sure we are definitely going in the right direction.

Of course, there may be times when we modify these functionalities, add new ones, or remove those that are no longer needed. Migration to a new website is a great time for such cleanup. We are then operating on a web page that is not yet visible in production and not used by users. 

Verifying ready WordPress to Drupal migration

Once the migration process is complete, we focus on key aspects to ensure the new website runs smoothly and aligns with our expectations. Here are some steps to follow.

Integrations with external services and APIs

We make sure that any integrations with external services are correctly configured, work as previously agreed, or are adapted to new requirements. We also primarily verify that API connections are working without problems.

Content and media control

We verify that the content on the new website is consistent with the original data. It’s also essential to check that images, videos, and other media are displayed properly. We ensure there is no lost data and that all content has been appropriately transferred from WordPress.

Monitoring of errors and logs

During testing, we regularly check Watchdog (a system for logging activity and errors within Drupal) for any post-migration errors. We also check for unexpected error messages from, for example, PHP when browsing the website.

SEO and page load speed optimization

We check that Drupal's SEO modules are configured correctly, and we monitor the page speed and adjust it if necessary by, for example, aggregating styles and JavaScript.

Testing functionality and permissions

We test the most important functionalities of the website, especially those related to user interaction. We make sure that all content permissions have been properly configured.

Responsiveness and device compatibility

We verify that the website is responsive and displays correctly on different screens after the migration. We confirm that the frontend works as expected on all devices used. We remember to look at all sensitive functionalities, such as forms or tables, which may have particular problems on narrower screens.

Quality audit and testing

We conduct a quality website audit. Tools such as CodeSniffer and PHP Stan will help us with this. We also ensure the web page goes through the automated testing process (Codeception) and is integrated into our pipeline.

Reproduction of the entire functional process

As part of the migration from WordPress to Drupal, the final step is to precisely recreate the entire functional process or processes. Rather than limiting ourselves to specific functions, we focus on ensuring the continuity of the user experience through a comprehensive flow that we define in the test scenario.

For example, it could be going through the entire form to register a new user, assign permissions, and adjust assigned roles and access levels to reflect previous structures. We can also focus on the process of creating new content. We take care to maintain compatibility with the structure and content types of the prior system, enabling a smooth transition for administrators and end users.

Reproducing the entire functional process not only ensures that the various elements work correctly. It’s essential to go through scenarios consisting of functionalities not included in Drupal core but explicitly written for the website. Often, these are key elements of business logic, and we need to be sure that all essential functions will work perfectly after their implementation. Finally, we don't want to get hundreds of emails from users that something isn't working after the migration.

WordPress to Drupal migration - summary

Migrating WordPress to Drupal is a complex process justified by various factors. One CMS may be chosen over another due to advanced configuration capabilities, better security, access control, performance, and scalability.

During the migration, it’s worthwhile to customize the new web page and its functionalities to meet specific needs and optimize URL structure or design. By focusing on a quality audit, automated testing, error monitoring, and adjusting responsiveness, we can ensure a smooth website implementation.

Moving a WordPress site to Drupal is a complex process that requires planning, diligence, and testing. If you’re considering transferring your web page or need support on this topic, you can count on our help. We specialize in Drupal migration to higher versions and from other CMS systems.

Dec 21 2023
Dec 21

According to the World Quality Report, the largest research study assessing the current trends of QE practices, Product Quality Engineering (PQE) offers several benefits.

  • Accelerated Software Delivery: By catching bugs early and often, Product Quality Engineering helps avoid costly reworks and delays while ensuring faster time to market.
  • Conservation of Resources: Continuous testing and automation help identify issues before they become major problems, minimizing resource waste.
  • Optimized User Experience: PQE isn't just about testing; it's about building quality into every process. This leads to fewer bugs and a smoother user experience.

Product Quality Engineering Principles That Enable Faster Software Delivery

Product Quality Engineering (QE) follows foundational principles, shaping the creation and delivery of software products.

Quality Engineering Principles

Principle 1: Prevention Over Detection

Proactively identifying risks and implementing preventive measures to curb defects ensures smooth delivery.

Principle 2: Shifting Quality Left & Right

Quality is embedded throughout the development lifecycle for holistic effectiveness.

Principle 3: Collaboration & Teamwork

QE facilitates clear communication, shared ownership, early issue identification, continuous feedback, and automation to ensure efficient collaboration.

Principle 4: Continuous Improvement & Adaptation

Regularly refine processes, tools, and methodologies to uphold the highest quality standards.

Principle 5: Measurement & Analysis

Quality metrics such as defect detection rate, user satisfaction ratings, and Net Promoter Score (NPS) are utilized to evaluate practices and inform decisions for continuous improvement.

Principle 6: Promotion Of Quality Culture

Cultivating a collaborative environment where everyone takes ownership of quality and fostering continuous improvement 

Principle 7: Business-Driven Testing

QE empowers business owners with the tools and processes to participate in testing activities effectively.

Principle 8: Automation & Innovation

Automated tools and frameworks speed innovation by handling repetitive tasks and letting QA resources concentrate on feature development/rapid iteration. Continuous CI/CD pipeline testing ensures high code quality, cutting costs and time to market.

Principle 9: User-Centric Focus

QE actively addresses usability issues, prioritizes testing based on user needs, and seeks continuous feedback to ensure alignment with user expectations.

A Short Tale: How Spotify Delivered Product Quality At Speed

Spotify, the renowned music streaming service, offers a vast library of songs accessible to over 226 million active users. But it wasn’t long ago when Spotify faced obstacles with launching new features on time and scaling with user needs. Spotify's fragmented technology stack led to inefficiencies and delays, resulting in a 14-day deployment cycle and a 60-day onboarding time for new engineers.

To overcome the challenges, Spotify adopted Quality Engineering Productivity, focusing on optimized code, effective collaboration, efficient resource utilization, and rapid development. 

This was made possible by developing and implementing 4 products

  • Backstage: A centralized repository for resources and tools to streamline development and improve collaboration.

  • Golden Paths: standardized bootstrapping for backend services, offering pre-defined configurations and tutorials to ensure faster deployment time and better environment management. 

  • Tingle CI/CD: Automates build, deployment, and release within GitHub, freeing engineers for innovation and strategic initiatives.

  • Test-Certification Program: Gamified testing speeds up QA by reporting build time, code coverage, and reliability. It alerts teams to unreliable tests and rewards achievements with badges.

Quality Engineering Delivery CyclesSource: aws.amazon.com

Consequently, Spotify could handle tens of thousands of builds weekly with shorter delivery cycles. The project setup time for experienced and new developers was reduced to less than 5 minutes.

Quality engineering is about establishing a systematic approach that cultivates a quality-focused mindset, ultimately leading to effective solutions.

How To Get Started With Product Quality Engineering

Product Quality Engineering (PQE) encompasses a wide range of activities, from defining quality standards to monitoring performance and identifying areas for improvement.

Step 1: Describe Quality Objectives

Establish clear, measurable objectives aligned with organizational goals and customer requirements. Identify key performance indicators (KPIs) to track and measure progress towards these goals.

Step 2: Define The Quality Criteria

Gather and analyze customer needs and expectations to define quality criteria. Develop a comprehensive understanding of customer requirements to ensure the final product or service meets expectations.

Step 3: Develop A Quality Plan

Develop a detailed plan outlining the entire quality assurance process. Specify quality control measures, testing procedures, and acceptance criteria.

Step 4: Design For Quality

Integrate quality considerations into the design phase to prevent defects and issues. Conduct design reviews to ensure that products or services meet quality standards.

Step 5: Establish Quality Control

Establish robust quality control processes. Conduct inspections, reviews, and testing at various production or service delivery stages.

Step 6: Continuous Improvement

Implement a culture of continuous improvement by regularly reviewing processes and identifying areas for enhancement. Utilize feedback mechanisms to gather information on defects, customer complaints, and other quality issues.

Step 7: Training & Skill Development

Provide training programs to ensure employees have the necessary skills and knowledge to produce high-quality output.

Step 8: Use Tools & Techniques

Leverage powerful Quality Engineering tools like Jira, Selenium, and Jenkins alongside methodologies like Six Sigma and Statistical Process Control (SPC). Also, implementing Failure Mode and Effects Analysis (FMEA) ensures effective, early issue identification and resolution.

Step 9: Document & Analyze Data

Maintain detailed records of quality-related data, including test results, inspections, and customer feedback. Analyze the data to identify trends, defects' root causes, and improvement opportunities.

Step 10: Customer Feedback & Satisfaction

Monitor customer satisfaction levels and use feedback to drive continuous improvement efforts.

Step 11: Certifications & Compliance

Obtain relevant certifications and ensure compliance with industry standards and regulations.

The order of these steps is flexible and can be adapted to best fit your specific project needs. 

How Axelerant Helped Build A Quality-First Portal For One Of USA’s Most Innovative Retail Brands

The customer is a leading American retail company that serves office supplies to businesses through superstores, retail markets, postal, Internet, and partner distribution networks. They needed a portal as a unified hub for its suppliers, merchants, and monetization teams.

The customer offers many programs for suppliers to advertise themselves and improve their brand awareness and sales. Their Partnership Portal would allow stakeholders to attend events and act as a complete campaign management tool.

Axelerant adopted a phased approach, initiating the timely launch of a Minimum Viable Product (MVP), a supplier-facing website for the client's Supplier Summit. 

The QE team conducted in-depth requirement analysis, facilitated swift product owner decisions, and ensured the project's phased implementation, contributing significantly to a smooth and timely delivery.

At Axelerant, our experts can help you navigate similar challenges and deliver exceptional products.

Schedule a call to learn more.

Dec 21 2023
Dec 21

When we was implementing a customer relationship management for a local client using Drupal, we faced this challege:

  • The client wanted to manage records of his patients with many treatment images captured by his iPhone
  • Each iPhone images can be large, 5 to 10MB depending on resolutions
  • While we can't hold so many large images to the web server, the disk space will soon run out

So we have to find a way to resize and compress images quality on the client side before uploading so they won't place burdens to the web server.

After googling around, we found a tip on this thread mentioning DropzoneJs. So we tried it and successfully reduce image sizes to 10% (from 3M to 300kb) while maintaining image quality.

In this tutorial, we will show you how to configure DropzoneJs in Drupal 10 to resize and compress images on client sides before uploading.

1. Install DropzoneJS and Entity Browser:

Please follow instructions to install DropzoneJS and Entity Browser modules

Download DropzoneJs and Exif-JS libraries and place them to /libraries folder, so the js files can be accessed as:
/libraries/dropzone/dist/min/dropzone.min.js
/libraries/dropzone/dist/min/dropzone.min.css
/libraries/exif-js/exif.js

Enable DropzoneJs and Dropzone Entity Browser widget, it will also enable Entity Browser module, as below:

Enable Dropzone modules

2. Create a Dropzone browser:

Please browse Admin - Configuration - Content authoring - Entity Browsers and Add a new Entity browser, name it Dropzone for example, and leave default settings.

Add Dropzone browser

Name Dropzone browser

On the next screen "Widget settings", please select Dropzone.

Select Dropzone browser

On the form below, please check the option "Use client side resizing" (note: it won't be checkable if you don't install Exif library as on step 1), then you will be able to select max width, max height and resize quality. We put 1920px for width and height, then image resize quality as 0.8 on our case.

Configure Dropzone browser

Save it.

3. Configure file upload widget

On your content type with image fields, please choose Manage form display, and set the widget to Entity browser

Set Manage form display to Entity Browser

4. Test the upload:

Now edit a node with image fields, the DropzoneJs browser is now displayed:

Dropzone browser display

We uploaded a test image, a fullsize image from our mobile camera, 3MB.

Upload an original image

After uploading successfully, we checked that file on our server, it was reduced to 300K, only 10% of the original size.

Image compressed

It is done. Now you can use DropzoneJs to resize and compress images on client sides before uploading, which is great for your web server.

Dec 20 2023
Dec 20

Introduction

Old Spice faced an image crisis, overshadowed by competitors like Axe. 

The turning point arrived in 2010 when an advertising campaign changed the entire narrative.

Procter & Gamble (P&G), recognizing the need for a major shift, hired Wieden+Kennedy. They worked towards creating a popular campaign - "The Man Your Man Could Smell Like.” 

The goal was to increase body wash sales by 15%, but by May 2010, sales of Old Spice Red Zone Body Wash had increased 60% from the previous year. By July 2010, sales had doubled.  

This example demonstrates how organizations can leverage external agency expertise to achieve success. 

Logo Old Spice - Before and After

Benefits Of Partnering With External Design Agencies

Hiring an external design agency can help organizations solve challenges and drive multiple benefits.

Challenges Faced By Digital Product Teams

Solutions Offered By External Design Agencies

Limited Expertise  Limited Expertise

In-house teams may not have multidisciplinary knowledge. External design agencies with expertise across diverse niches can step in and work as an extension of the in-house team.

Apple, known for products with top-notch design, collaborated with Foster + Partners to create Apple Park.

Creativity Constraints Creativity Constraints

Finding new ideas for every project can be tough. Instead, organizations might want to spend more time on driving growth.

A simple solution is collaborating with an external design agency that would bring new viewpoints and creative ideas.

The “#ShareACoke” campaign created by Ogilvy boosted Coca-Cola's U.S. sales by 2.5%. After a decade of decline, this marked a significant turnaround for the beverage company.

Encouraging collaborative brainstorming sessions between in-house teams and external design agencies also helps foster creativity and idea generation.

Budget Constraints Budget Constraints

Organizations often face financial limitations, which impact their design projects. For example, a small startup may have limited funds for design, while an enterprise may face overall budget restrictions.

Hiring an external design agency brings cost-effectiveness while ensuring the right expertise.

Scalability Issues Scalability Issues

Organizations with aggressive growth goals and multiple client projects need help scaling their in-house design efforts.

For example, they often need to work on multiple client projects with conflicting deadlines, challenging their ability to deliver high-quality work consistently. The organization’s bandwidth becomes limited when the team already has its hands full, and a new client may be onboarded.  

Collaborating with external design agencies gives organizations access to a diverse pool of resources that can be quickly scaled up to meet tight deadlines for in-house and client design needs.

Implementing The Latest Design TrendsImplementing The Latest Design Trends

Organizations struggle to keep up with design trends as it is time-consuming. Hiring an external agency is the right solution as it helps them stay relevant and ahead of their competition.

External design partners would prioritize continuous improvement, staying on top of industry trends, user feedback, and emerging technologies. 

UK-based digital marketing agency Nativve has been Patagonia's marketing agency since 2015. They've collaborated with Patagonia Europe, executing digital marketing strategies, launching products and stores, and supporting environmental projects such as - Save the Blue Heart of Europe and Shell, Yeah!

Changing Business Objectives Changing Business Objectives

Organizations may find it challenging to align design efforts with evolving business goals, such as increasing visibility or improving customer loyalty and retention.

External design agencies enable organizations to adapt to changing requirements and meet their business goals.

For example, a skilled design agency partner can help modernize the digital platforms of universities, streamlining the navigation and enhancing overall usability.

This approach boosts user satisfaction and engagement and aligns with crucial business objectives such as attracting top-tier students and optimizing administrative processes.

Inconsistent Designs Inconsistent Designs

Consistency is the key to a seamless user experience. Organizations often struggle with design consistency during rapid scaling.

External agencies help organizations maintain consistent design, especially during rapid scaling. Their expertise ensures a cohesive brand appearance across platforms.

Lack Of User Feedback Lack Of User Feedback

Some organizations may need help to gather direct feedback from users.

External design agencies help bridge the gap by employing user research techniques like surveys, user testing, and analytics. 

The research offers valuable insights and data, ensuring user-centric design solutions and improved products.

A Short Tale: How N.W Ayer & Sons Helped De Beers Create A Historic Campaign

The De Beers Group hired ad agency N.W. Ayer & Sons in 1938 to help boost their diamond sales.

As a result, between 1938 and 1941, the brand reported a 55% increase in the U.S. diamond sales in just four years. But the most incredible breakthrough occurred in 1947 when the agency's copywriter Frances Gerety penned the iconic tagline 'A Diamond Is Forever.'

As a result, from 1939 to 1979, De Beers's wholesale diamond sales in the United States increased from $23 million to $2.1 billion. The association of diamonds with engagement rings and the change in consumer psychology played a huge role in the roaring success.

De Beers Sales 1979

How To Choose An External Design Agency

There are a few basic steps that you can take to choose an external design agency.

Step 1: Identify Your Business Goals

Identify your business goals and clearly envision a productive partnership with a design agency. Whether you want a UX audit or revamp your website's user interface, articulating your objectives will help you select the right agency.

Step 2: Evaluate Agency Portfolios

A design agency's portfolio is a reflection of its capabilities. Evaluate their portfolios by paying attention to their quality, creativity, and problem-solving prowess. 

For example, an I.T. company seeking external agency help with an app redesign should ideally partner with a design agency with a proven track record in designing mobile and web apps.

Step 3: Check For Relevant Industry Experience

Assessing industry-specific expertise is essential when choosing a design agency. This is because agencies familiar with the nuances and challenges of your particular sector are equipped to deliver a tailored solution. 

Step 4: Evaluate Agency Values & Culture Alignment

Find agency partners that share your values to build a stronger synergy within the team. For example, organizations with sustainability as a core value should collaborate with agency partners that share this commitment.

Step 5: Consider Agency Size & Scalability

The size and scalability of an agency should correspond to your project's scope and demands. A large enterprise with complex design needs may prefer an agency with ample resources. Meanwhile, a small startup could benefit from a more agile and cost-effective partner that can scale with them.

Step 6: Review Client Testimonials & Case Studies

Testimonials and case studies provide a window into an agency's record of client satisfaction and project success. These resources demonstrate valuable insights, such as the agency's ability to deliver on promises and meet or exceed expectations.  

Step 7: Seek Referrals & Recommendations

Referrals and recommendations from trusted sources within your professional network can be invaluable. You can leverage renowned design communities like the British Interactive Media Agency and Interactive Design Foundation.

Step 9: Request Proposals & Quotes

Contact shortlisted agencies and request detailed project proposals and cost estimates. The thoroughness of these proposals is critical to check whether they fit all your requirements. 

Step 10: Evaluate Pricing  

When choosing an external design agency, the fit between cost estimates and financial parameters is essential, especially for startups or non-profits with limited budgets.  

Step 11: Conduct Multiple Meetings  

Discuss project methodologies, approaches, and team dynamics with your design agency partner. These interactions provide an opportunity to establish rapport, gain a deeper understanding of their approach, and understand their alignment with your vision and objectives.

Step 12: Check Engagement Models

When assessing potential agencies, it's crucial to examine their engagement models. An agency with multiple, flexible engagement models is preferable. These help tailor their approach to your unique needs and ensure a more precise alignment of services.

How Axelerant Designed An Intuitive And Personalized Experience For the University Of East London (UEL)

A higher educational institution with a vision to reshape education, UEL needed its website to be more effective. The website required Drupal migration for better performance, user experience, and security.  

The two main asks for this project were:

  1. Rethinking The Information Architecture: To help users find the information they need quickly and efficiently.  
  2. Redesigning User Interface Elements: To align with the university’s vision and tone and bring in consistency.

Axelerant initiated the information architecture and website redesign strategy with Design Thinking and used key research techniques like User Journey Mapping and Stakeholder and User Mapping. Together with the UEL's digital team, we also worked on the heuristics audit feedback received from the U.K. government. 

The joint efforts helped create a more efficient user experience platform for the users of the University of East London’s website with tangible outcomes.

The university saw an increase in student applications and the website ranked 22nd from the previous 100+ rank among UK universities per Silktide’s (accessibility) rating in 2022. 

UEL also got shortlisted for Times Higher Education Awards 2023 in the Technological or Digital Innovation of the Year category.

Need more clarity on choosing an external design agency partner that aligns with your vision? We’re here to guide you through the process.

Dec 19 2023
Dec 19

Drupal 10 celebrated the one year anniversary of it's release this month, and despite both Drupal 8 and 9 having reached end of life, the Drupal Community's pace of upgrading to Drupal 10 lags far behind where it needs to be. 

There's much to love about Drupal 10, and Promet recently assembled three of our top in-house experts on the topic for a free webinar designed to answer the big questions and help remove barriers to upgrading ASAP. Click the image to head over to the conversation! 

AMA Drupal 10 webinar on demand

Recap of main points

  • A Drupal 10 upgrade can have a direct and highly positive impact on site performance. 
  • Drupal 10 is the best content management system on the market for multilingual support.
  • The Nov. 1, 2023 end-of-life date for Drupal 9 marked a hard stop for community updates and security support.
  • Integrations with other tools become increasingly challenging, risking possible breakages, when working with legacy versions of Drupal.
  • Upgrade delays translate into increased technical debt, which means that the further you stray from maintained software, the more you rely on workarounds and unproven in-house solutions.
  • There is not a direct migration path from Drupal 7 to Drupal 10. The strategy we recommend is to build a new Drupal 10 site and migrate Drupal 7 content over to it. 
  • An essential advantage of Drupal 10, and one that is seldom discussed, is that upgrading secures your future and positions you for huge benefits that are in store as the Drupal 11 release date nears: Project Browser, Automatic Updates, and the integration of Gutenberg as an editor inside of Drupal.

We barely had the chance to scratch the surface in the time allowed. Have a question about a Drupal 10 upgrade or the specific advantages of the current version? Just let us know. We're happy to help!

Dec 19 2023
Dec 19

Janez Urevc

Strategic Growth and Innovation Manager

Janez was first exposed to free software in elementary school and has been passionate about it since. He has been involved with Drupal since his introduction while completing a computer science degree. Between 2010 and 2012 he led the transition to Drupal 7 at one of the biggest publishing companies in Slovenia. After that he joined Examiner.com which was the biggest Drupal website at the time. He started contributing to Drupal 8 early in the release cycle and due to his experience in the publishing industry became active in the media initiative and eventually became its lead. Janez is, besides free software and Drupal, passionate about challenging technical problems, complex computer systems, management processes, investing, and most sports that include putting some kind of a board below one's feet.

Dec 19 2023
Dec 19
Sean B5 min read

Dec 19, 2023

Let’s face it — manually creating and maintaining a lot of content types in Drupal can be a real pain. The repetitive nature of tasks, inconsistencies in field creation, and the time-consuming process of updating settings across multiple content types are familiar struggles for developers working in teams.

At TwelveBricks, we maintain sites with very different content models. Too much time was spent setting up and maintaining those models, so we finally decided to do something about it!

YAML Bundles is a pragmatic approach to streamlining content type management through YAML-based Drupal plugins. It allows developers to define fields and content types from custom modules, making it a lot easier to add or update fields and content types.

The module defines 3 important plugin types:

  • Field types
    By defining common field types and their form/display settings (like widgets and formatters), you can remove repetitive configuration from the bundle definitions.
  • Entity types
    You can define common settings for entity types to remove even more repetitive configurations from the bundle definitions. We have also integrated support for a bunch of other contrib modules we often use, to save even more time.
  • Entity bundles
    You can use the defined fields in entity bundles, complete with customizable form and display settings that can be overridden on a field-by-field basis. The default settings of the entity types can also be overridden if you need to.

The module ships with defaults for the most common field and entity types, but you can easily override them. We have tests, which we also used to document all the available plugin settings and options. You can check out the documentation in the plugin definitions of the yaml_bundles_test module.

To use the power of YAML bundles, we’ve added a couple of helpful Drush commands that will revolutionize your content type management.

drush yaml-bundles:create-bundles
This command uses the defined plugins to (re)create all the specified bundles, fields, and settings. Whether you’re starting from scratch or optimizing an existing configuration, this command ensures the seamless generation of all your content types with a single command.

drush yaml-bundles:create-bundle
You don’t have to use the bundle plugins if you don’t want to. For those who prefer a more hands-on approach, this command offers the flexibility to create a new content type directly through Drush.

drush yaml-bundles:create-field
This command simplifies the process of adding new fields to existing content types using default plugin configurations. With the ability to customize form and view displays, this command makes adding fields to different content types much easier.

YAML Bundles comes with out-of-the-box support for several popular Drupal modules, including:

  • Pathauto: Define path aliases for entities and bundles effortlessly.
  • Content Translation: Translate fields, labels, and settings into multiple languages.
  • Content Moderation: Integrate content types into existing workflows.
  • Field Group: Group fields for your content type directly from the YAML plugins.
  • Simple Sitemap: Ensure your entity/bundle is included in the sitemap.
  • Maxlength: Define maximum lengths for text fields if needed.
  • Layout Builder: Utilize layout builder for your entity/bundle.
  • Search API: Index entities/bundles and defined fields using the powerful Search API.

To define default settings and fields for an entity type in your custom module, create a [mymodule].yaml_bundles.entity_type.yml file. In this file, you can create all the defaults you need for the entity type.

# Add the default settings and fields for node types.
node:
description: ''
langcode: en

# Make sure our plugin gets preference over the yaml_bundle plugin.
weight: 1

# Default content type settings.
new_revision: true
preview_mode: 0
display_submitted: false

# Enable content translation.
content_translation:
enabled: true
bundle_settings:
untranslatable_fields_hide: '1'
language_alterable: true
default_langcode: current_interface

# Add the content to the sitemap by default.
sitemap:
index: true
priority: 0.5
changefreq: 'weekly'
include_images: false

# Enable layout builder.
layout_builder: true

# Enable content moderation.
workflow: content

# Add the content to the default search index.
search_indexes:
- default

# Create the full and teaser view displays.
view_displays:
- teaser

# Add the default fields.
fields:
langcode:
label: Language
weight: -100
form_displays:
- default
title:
label: Title
search: true
search_boost: 20
weight: -98
form_displays:
- default
field_meta_description:
label: Meta description
type: text_plain
search: true
search_boost: 20
maxlength: 160
maxlength_label: 'The content is limited to @limit characters, remaining: @remaining'
weight: -25
form_displays:
- default

# Translate the default fields.
translations:
nl:
fields:
langcode:
label: Taal
title:
label: Titel
field_meta_description:
label: Metabeschrijving
maxlength_label: 'De inhoud is beperkt tot @limit tekens, resterend: @remaining'
de:
fields:
langcode:
label: Sprache
title:
label: Titel
field_meta_description:
label: Meta-Beschreibung
maxlength_label: 'Der Inhalt ist auf @limit-Zeichen beschränkt, verbleibend: @remaining'</span>

To define a content type in your custom module, create a [mymodule].yaml_bundles.bundle.yml file. In this file, you can create all the bundle definitions you need for the bundle.

node.news:
label: News
description: A description for the news type.
langcode: en

# Add generic node type settings.
help: Help text for the news bundle.

# Enable a custom path alias for the bundle. Requires the pathauto module to
# be enabled.
path: 'news/[node:title]'

# Configure the simple_sitemap settings for the bundle. Requires the
# simple_sitemap module to be enabled.
sitemap:
priority: 0.5

# Configure the search API index boost for the bundle. Requires the
# search_indexes to be configured and the search_api module to be enabled.
boost: 1.5

# Configure the fields for the bundle. For base fields, the field only needs
# a label. For custom fields, the type needs to be specified. The type
# configuration from the yaml_bundles.field_type plugins will be merged with
# the field configuration to allow the definition to be relatively simple.
# Generic defaults for a field type can be configured in the
# yaml_bundles.field_type plugins.
#
# See yaml_bundles.yaml_bundles.field_type.yml for the list of supported
# field types and their configuration properties.
fields:

field_date:
type: datetime
label: Date
required: false
search: true
search_boost: 1
cardinality: 1
field_default_value:
-
default_date_type: now
default_date: now
form_displays:
- default
view_displays:
- full
- teaser

field_body:
type: text_long
label: Text
required: true
search: true
search_boost: 1
form_displays:
- default
view_displays:
- full

field_image:
type: image
label: Image
required: true
search: true
form_displays:
- default
view_displays:
- full
- teaser

field_category:
type: list_string
label: Category
required: false
search: true
options:
category_1: Category 1
category_2: Category 2
form_displays:
- default
view_displays:
- full
- teaser

field_link:
type: link
label: Link
required: true
search: true
cardinality: 2
form_displays:
- default
view_displays:
- full
- teaser</span>

We’re excited about the potential YAML Bundles brings to the Drupal ecosystem, and we can’t wait for you to experience the difference it makes in your projects. Please check it out and let us know what you think!

Download YAML Bundles | Documentation | Example plugins

Building a new website can be a time-consuming and expensive process, but it doesn’t have to be. TwelveBricks offers an affordable and easy-to-use (Award-winning!) Content Management System (CMS) for a fixed monthly price.

We’ve optimized the workflow for editors so they can fully focus on their content without worrying about the appearance. With built-in analytics and extensive SEO tools, optimizing content becomes even easier. Each website can be delivered in just 2 days!

If you’re looking for a new website and don’t want to wait for months, spend a fortune, or compromise on quality, check out our website at https://www.twelvebricks.com/en.

Dec 18 2023
Dec 18

Our client, ServiceNSW, is a committed open-source contributor, working closely with us to improve their customer experience while sharing these advances with the Drupal community.

How is client-backed contribution made possible? 

It helps when you work with a client that understands the value of contributing development time back to the Drupal community. ServiceNSW are members of Drupal and have co-presented with us at DrupalSouth, so they’re truly invested.

ServiceNSW Contributions

Solutions to client challenges, such as core patches or contributor modules, require upfront work. Doing this in a community setting is far more beneficial, allowing everyone to contribute and further improve it. That’s why SNSW recognises the future benefits of investing in the work done now. 

We also put a lot of focus on performance and security. This means SNSW receives the latest upgrades for both Drupal core and contributed modules, helping move issues along and ensuring they have the latest and greatest, including being one of our first clients to move to Drupal 10.1. In fact, during the lead-up to the release of Drupal 10.1, we committed over a dozen large core issues in collaboration with the SNSW development team.

The patches we worked on pre Drupal 10.1 upgrade

Over a period of three months, in the lead-up to Drupal 10.1, we targeted patches that were large and/or conflicted with other patches we were using. These were becoming increasingly hard to maintain. SNSW understood that these fixes would be a net gain in developer productivity and an improvement for the community.

  1. Issue #3198868: Add delay to queue suspend
  2. Issue #2867001: Don't treat suspending of a queue as erroneous
  3. Issue #2745179: Uncaught exception in link formatter if a link field has malformed data (a 7-year-old bug!)
  4. Issue #3059026: Catch and handle exceptions in PathFieldItemList
  5. Issue #3311595: Html::transformRootRelativeUrlsToAbsolute() replaces "\r\n" with " \n"
  6. Issue #2859042: Impossible to update an entity revision if the field value you are updating matches the default revision
  7. Issue #2791693: Remove sample date from date field error message and title attribute (another 7 year old one!)
  8. Issue #2831233: Field tokens for "historical data" fields (revisions) contain a hyphen, breaking twig templates and throwing an assertion error
  9. Issue #3007424: Multiple usages of FieldPluginBase::getEntity do not check for NULL, leading to WSOD

Revisions everywhere!

One of our largest pieces of work was Implementing a generic revision UI

Originally opened in 2014, this issue paved the way for one of the most sought-after features from our client - having Revisions for all entity types and a consistent user experience for them.

This was originally committed to the SNSW codebase in July of 2018 using this patch when we added a Block Content Type for a Notice feature on the website.

~3.5 years, ~250 comments, and a huge effort from PreviousNext and SNSW developers, along with many other community members and it was committed to 10.1.x.

This spawned several other core issues for other entity types:

  • Block Content - This was also committed to 10.1 alpha.
  • Media - which is committed and will be available in 10.2.0!
  • Taxonomy terms - which is currently RTBC and looking promising for 10.3!

Plus contributed projects to extend contributed module entity types with revisioning support, such as Micro-content Revision UI.

The patches committed to Drupal 10.1 that we were able to remove

With all this pre-work, we were well positioned when the 10.1 upgrade came around. As you may have noticed, we like to get the ball rolling early, and we had a Pull Request going for the 10.1 upgrade in late June (the day 10.1.0 was released, in fact). This allowed us to figure out which modules needed help, what patches needed re-rolling, and to catch any bugs early.

It wasn't until mid-August when that PR was finally merged, with multiple developers touching it every now and then, when there was some movement.

Here's a full list of Drupal core patches we were able to remove, thanks to the contributions from SNSW.

  1. Issue #2350939: Implement a generic revision UI
  2. Issue #2809291: Add "edit block $type" permissions
  3. Issue #1984588: Add Block Content revision UI
  4. Issue #3315042: Remaining tasks for "edit block $type" permissions
  5. Issue #2859042: Impossible to update an entity revision if the field value you are updating matches the default revision
  6. Issue #3311595: Html::transformRootRelativeUrlsToAbsolute() replaces "\r\n" with " \n"
  7. Issue #3007424: Multiple usages of FieldPluginBase::getEntity do not check for NULL, leading to WSOD
  8. Issue #2831233: Field tokens for "historical data" fields (revisions) contain a hyphen, breaking twig templates and throwing an assertion error
  9. Issue #3059955: It is possible to overflow the number of items allowed in Media Library
  10. Issue #3123666: Custom classes for pager links do not work with Claro theme
  11. Issue #2867001: Dont treat suspending of a queue as erroneous
  12. Issue #3198868: Add delay to queue suspend
  13. Issue #2984504: Access to 'Reset to alphabetical' denied for users without administer permission
  14. Issue #3309157: RevisionLogInterface is typehinted as always returning entity/ids, but cannot guarantee set/existing values
  15. Issue #2634022: ViewsPluginInterface::create() inherits from nothing, breaking PHPStan-strict
  16. Issue #3349507: DateTimePlus::createFromDateTime should accept DateTimeInterface
Drupal 10.1 Patch changes

Service NSW, a true Drupal partner

Service NSW has (at the time of writing this post) contributed to 19 Drupal core issues that were committed over the past three months.

We look forward to continuing this incredible partnership and contributing in the coming months!

Dec 18 2023
Dec 18

When I joined the Drupal Association in July, I underestimated how moved I would be by the collective power of the community. A throwback to my organizing roots, I reveled in the eclectic excitement surrounding the innovation and collaboration of the application, evolution, and marketing of Drupal.

I remember discovering open source software myself, over 10 years ago. The worker’s center I worked for housed an instance of CiviCRM in Drupal and we used it to track our members — as we served a vulnerable population, it was paramount to keep the data safe and away from clandestine subpoenas and prying eyes.

Drupal responds to a fundamental need in the nonprofit sector – the ability to own, control, and share data. Joining the Drupal Association as the Director of Philanthropy allows me to work within the nonprofit sector to leverage the power of Drupal for greater impact, and I yearned for an opportunity to collaborate with others with the same perspective.

The Drupal Association was remiss to let the Nonprofit Summit lapse at DrupalCon Pittsburgh, but… I am thrilled to reintroduce the Nonprofit Summit at DrupalCon Portland!

The network of nonprofits in the Drupal Community is strong and vibrant and has been a joy to work with and learn from. Judging by the extraordinary talent represented by its organizers, Jess Snyder and Johanna Bates, the Nonprofit Summit will be a dynamic and inspiring one-day event bringing together passionate professionals from the nonprofit sector to delve into the transformative potential of Drupal.

Join us for a day of discovery, collaboration, and inspiration as we collectively unlock the full potential of Drupal for social good. Facilitated discussions, round table group sessions, and an opportunity to learn and inspire one another are just a few of the features we plan to bring to the summit this year.

The Nonprofit Summit will be on Thursday, 9 May, the 4th day of DrupalCon, after three days of expert speakers, networking, and contribution. Tickets go on sale 6 February. And we’re especially pleased to announce that the Drupal Association will subsidize the cost of tickets for those in the nonprofit sector, offering special pricing for the conference and summit! The conference rate for nonprofits is $395 and includes the summit.

Mark your calendars, spread the word, and get ready to be part of a community dedicated to making a lasting impact. The Nonprofit Drupal Summit is back and ready to shape the future of digital philanthropy. See you there!

Dec 18 2023
Dec 18

Today we are talking about the Drupal Association Board, Its Strategic Initiatives, and The Future of Drupal with guest Baddý Sonja Breidert. We’ll also cover Advent Calendar as our module of the week.

For show notes visit:
www.talkingDrupal.com/429

Topics

  • Former member of Board of Drupal Association
  • What does the board do
  • How does the board operate
  • Are there term limits
  • How does someone get on the board
  • Strategic Initiatives
    • Innovation
    • Marketing
    • Fundraising
  • Now that you are no longer on the board what’s next
  • CEO of 1xInternet
  • How did you get started with Drupal

Resources

Guests

Baddý Sonja Breidert - 1xinternet.de/en baddysonja

Hosts

Nic Laflin - nLighteneddevelopment.com nicxvan
John Picozzi - epam.com johnpicozzi
Ron Northcutt - community.appsmith.com rlnorthcutt

MOTW

Correspondent

Martin Anderson-Clutz - @mandclu
Advent Calendar

  • Brief description:
    • Have you ever wanted to reveal content a day-at-a-time, in an interactive advent calendar? There’s a module for that.
  • Brief history
    • How old: created less than month ago in Nov 2023 by listener James Shields, whose drupal.org username is lostcarpark
    • Versions available: 1.0.0-beta3 release, which works with Drupal 10.1 and newer
  • Maintainership
    • Actively maintained, latest release made earlier today
    • Test coverage
    • Number of open issues: 5, 3 of which are bugs, but all but one are now marked as fixed
  • Usage stats:
  • Module features and usage
    • James actually created a Drupal advent calendar a year ago, on his website lostcarpark.com. The idea was to showcase a new module every day, similar to advent calendars that provide a chocolate or a toy each day, hidden behind a cardboard door
    • James’ initial version displayed the content in a traditional calendar format, using the Calendar View module. What he really wanted, however, was a way to present the content using clickable doors to reveal new entries
    • The new Advent Calendar module provides a new view display, so you can configure what content type or other filters to apply, and use fields to specify what information to show
    • The module uses a Single Directory Component for display, hence the 10.1 requirement
    • There is also an “Advent Calendar Quickstart” submodule that sets up everything for you, including a content type, view, and 24 nodes to populate it for you
    • Each site visitor gets to “open” the door to new content as it is published each day. For authenticated users, which doors have been opened is stored as user data, and for anonymous users it’s kept in local storage via Javascript
    • In addition to this being an interesting module in its own right, the advent calendar James has created this year is also a community effort. He’s managed to enlist a wide variety of contributors to write about modules or aspects of the Drupal community that they’re passionate about, so it’s a great way to up your Drupal game. You can open a new door yourself every day at https://lostcarpark.com/advent-calendar-2023

Pages

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web