The summer of 2021 resulted in two releases in the world of Craft CMS—one big one for me and one big one for everyone else! In July there was the release of Craft CMS 3.7 and about a month later I released version 3 of the Craft CMS plugin, Guide. Regarding the Guide release, I had been planning on its release for months, so I put out what I felt was a good GA release, but I had plenty of plans for improvements and features that I planned on working on soon after.

What was released in Craft CMS 3.7 was one of the best and worst features for my newly minted plugin.

Slidouts

Along with a bunch of other author-experience features, Craft CMS 3.7 introduced the slidout UI for making it easier for authors to make changes to related entries, image assets, and other related elements. You could double-click just about any entry element in the UI and it would open up a full editor in the slideout panel—letting you make changes to the entry, save them, and then when the slidout panel closed you’d be right back to the original entry you had begun with.

It was a really impressive feature and since one of the things I really wanted to promote in Guide 3 was the idea of putting important authoring information right on entry edit pages, having some sort of button in the sidebar that could open up related guides (content entries in the Guide plugin) in a slideout was an absolute no-brainer. I added slideouts to my backlog and planned on working on it for a 3.1 release.

It had been a little bit before I could make the time to work on the 3.1 branch and I remember having a little trouble figuring out how to work with the Craft.Slidout API at first. I found Leevi Graham’s excellent walkthrough on working with the Craft.CpScreenSlidout API and that helped me get a working prototype going. At least I could hack some of the way Guide moved content around the Craft CMS CP (Control Panel) to get guide content to appear on a Craft.Slideout panel. From there, that's where a bunch of problems became obvious around one of the basis on Guide’s architecture.

Relying Too Much on a Vue 3 Feature

The approach for Guide 3 came from what I was seeing in my own personal use of the Guide 2 plugin. The company I had worked for was still creating CMS guides (a lot of time we had a template and we brought it from site to site), but we were also doing lots of custom Craft CMS module work. Clients wanted a page in the CMS to keep track of things like image alt text or to have a widget on the Dashboard page that showed very specific data. Guide 2 let you do some of this and by Guide 3 I had re-engineered it so you could take one guide and spread it around several of the predefined locations in the CP, as well as on any page in the CP—and even on elements outside of the main #content area.

We were making custom alert banners and dropping buttons in the sidebar on element list pages. We'd do things that would make Brandon Kelly and the UI folks at Pixel & Tonic cry (probably), but these customizations were helpful for our clients and how they wanted to use their CMS.

All of this was driven by how Guide would let you put in any CP page URI (like seomatic/settings) and then any CSS selector you could find in the CMS’s HTML markup, and Guide would figure out how to get your page content to show up there.

Vue Teleport-Driven Development

This ability to move guides into various elements drove the main way that all guides were displayed on the page. While you may have seen a guide appear above the fields on an entry edit page, what was really happening is that Guide would render all of the guides at the bottom of the page from Twig, then use Vue 3’s Teleport component to take the guide content and throw it up to the correct element above. If you had some guides appear above the entry edit fields and then some guide content appear in another element, Guide would still render all of the guides at the bottom of the page, but I had mapped out all of the guides and where they were going and then I had Vue dispatch them upon mounting Vue.

I was pretty happy with this setup but there were a few thorns on that rose. For one, Vue Teleport required an element to be rendered at the point that Vue was mounted, or it would sort of say "hey, I want to teleport this thing to this element but the element doesn't exist yet... I guess we're done here...". At that point Vue would give up and your guide content wouldn't show up anywhere. I worked around this for some situations, but this was one of the bigger issues with trying to get guides into slideouts.

Making Messes in My Code Base

When I started the slideout prototype I had already started refactoring some things in my code base to migrate my Vue components from TypeScript with Vue’s Options API to using TypeScript and Vue’s Composition API. Anybody who has worked with both knows that the Options API with TypeScript wasn't nearly as straightforward as the Composition API approach. At one point I realized I had made a mess of things and that derailed things for a while. Some of it was my TypeScript implementation and some of it was me getting used to how to convert my existing code to Vue 3’s Composition API.

Also, rendering all guide content in Vue had another downside that I couldn't come up with an answer for. Vue likes it when Vue knows about everything it’s rendering. If done with coding guard rails in place you can get plain JavaScript to work with Vue, but it was really limiting with what you could do with JavaScript to customize guide content without completely breaking things. Craft has a couple of handy Twig tags, such as the {% js %} tag, that allow you to write custom JavaScript and while Guide would let you use them in your guide content (anything Craft CMS’s Twig could do you could do in Guide), but if you tried doing something like using vanilla JavaScript to sort rows in a table of data you'd be in trouble.

There were other problems with my implementation and I would create a branch, get stuck, and would scrap it for another attempt. I had thought about how to move forward within my code base for many months and finally came to the conclusion that some big rewrites would be needed.

Dropping Frameworks to Get (a Little Bit) Closer to HTML and Twig

Okay, fast forward to early 2024 when Craft 5 was entering beta. I had taken a break from the Guide problem and let that one sit on the back burner so I could show some love to my other two plugins, Admin Bar and Little Layout. Admin Bar had some of its own issues and the solution I went with for working with it across Twig and JavaScript SPA-like sites was to break it out into a standalone Web Component.

Little Layout also got the Web Component treatment as I converted the field and its settings from Vue 3 to using Web Components that wrapped around HTML elements that were rendered by Twig. The difference between waiting for DOM elements to be ready so you could vue.mount() an instance onto it versus registering a Web Component and having it automatically take care of kicking off when the custom element gets added onto the page was a big eye opener for me.

This is the thing that made Guide click for me and I got to thinking about how a refactor from Vue to Lit (my current Web Component framework of choice) might look.

Introducing Guide 5.0 (Beta)

This introductory post goes over the details around what’s new in Guide 5 at a high level.

What is new is that guides can now be shown in slideouts 🎉, and there are no hacks used to get them there. There's also a new guides list page that I hope will fix some of the confusion that the old Guide Organizer page created for some folks. I’ve re-written the entire UI side of things and even moved the documentation out of its own Nuxt-based docs website and I've put it right on the pages in Guide (you can also read them as markdown pages on GitHub if you prefer to read the docs that way).

More details around what has changed can be found on the plugin CHANGELOG on GitHub.

It Was a Good Idea at the Time

Guide 5 drops Tailwind and Vue 3 as dependencies. I don’t have a problem with either technology, but they no longer benefitted the project in the way I had hoped when I introduced them.

With Tailwind I had used it for a lot of the Vue component templates and I had offered a very small subset of Tailwind classes to guide authors as "Guide utility classes". It worked fine for my Vue work, but after thinking about it for a while I thought that it's not fair to offer Tailwind but to make customers do two things: they A. have to look up whether a class is available before using it, and B. they had to prefix everything with g- (so display: grid would be g-grid). I looked into ways of running guide content through some sort of Tailwind compiler, but I couldn’t find a good way to do that without a Node server or adding some pretty big dependencies to get things working in PHP.

Instead of using Tailwind I made it easier to write CSS for Guides by adding a code editor field that has some basic code completion and CSS syntax highlighting. For authors who just want to use Markdown I added a few more styles to cover common Markdown elements. For folks who do want to use CSS most of the default styles now come from Craft CMS itself and many of my overrides include CSS Custom Properties so it should be easy to override styles and spacing.

I think with all of the new features CSS has to offer, it is much easier to work with CSS directly. Features like :has() can be a huge help and I added a container to guide displays so you can use @container queries to make adjustments as guides appear in widgets and on full pages, alike.

Using the Platform

Regarding Vue, I was really invested in getting Vue Teleport to work and I thought at one point that if I needed to use Vue for some of it I may as well write everything in Vue. The problem with this is that if I wanted to do something with a native Craft CMS element, like form inputs, I either had to play a game of JavaScript tag between Vue and the native HTML components or I had to create my own components. I wound up creating Vue components that looked like the native text input and select components and they worked very similar by the time I was done.

The big problem, though, is that the version I created got out of date very quickly. To their credit, Pixel & Tonic have put a bunch of effort into making accessibility improvements and iterations on the design of the elements in the CP and my component clones weren’t getting any of it. By removing Vue’s rendering I wound up going back to using Craft CMS form elements (via their Twig macro) and now when you submit the form for something like editing a guide it's back to using a good old fashioned HTML form. Any reactive bits come in the form of adding event listeners and then maybe doing something within a Web Component and using slots to swap out content or visually hiding irrelevant input fields.

Because of the way Web Components are registered to the page and then initialized when they are added to the DOM, I got around the way I was using Vue Teleport to move guides around. In some cases guides are just rendered in place, but for guides that are added to places outside of the presets I am now using native JavaScript to take a handful of guide elements and then distributing them where they need to go. There's a Web Component that is just a wrapper around individual guide content that handles things like adding a menu when multiple guides are displayed in one spot. Otherwise guides are now just HTML rendered from Twig.

Guide component

Some of the Guide Twig components use Web Components, too, but those are added by guide authors using Twig, and the Web Components just enhance their functionality. For example, the image component will just display an optimized version of the image if you just pass in an asset parameter. Because it’s transformed to a smaller size it can be hard to see the details in something like a screenshot, so now there’s an parameter in the component function to make it so the image can be clicked on and the original image can be displayed in a modal.

Guide Editor
{# Adds an image from an uploaded asset and does an image transform to reduce the size and optimize it. #}
{{ craft.guide.component('image', {
    asset: craft.assets.filename('guide-widget.png').volume('guide').one()
}) }}

{# Adding the `modal` param wraps the output of this image in a web component that handles opening up the full size image URL in a modal when you click on it. #}
{{ craft.guide.component('image', {
    asset: craft.assets.filename('guide-widget.png').volume('guide').one(),
    modal: true
}) }}

The output is a <picture> tag with srcset used to handle multiple resolutions. If you add the modal option a Web Component wraps the <picture> element to add the modal functionality but it doesn't do anything to the image inside of it.

I really like this kind of thing. In working with Web Components for a third time now, I am starting to understand where Shadow DOM and Light DOM (non-shadow) have their own benefits. I also like the idea that I don’t have to worry about big framework shifts because Lit seems to be closely in tune with the web standards behind Web Components. Maybe someday these web components will simply follow web standards and I can remove Lit, but for now it includes a lot of great tools that makes working with Web Components very pleasant.

Customer Research-Driven Development

At one point all of the trouble with Guide’s source, and what looked like a big dip in sales, got me to think about how I wanted to proceed with the plugin. The trickiest thing is I have GitHub Issues and Discussions as my main point of getting feedback and while that helps show the holes, I wanted to know what people using the plugin actually thought and to know how they used it. In early 2024 I put out a survey in the form of a Google Form and while I got a few responses from that I also got some suggestions by folks via the Craft CMS Discord.

What I had learned was that Guide 3 was confusing. I had put together what I thought were clever redirects when you didn’t add certain settings, but other than in the docs somewhere, I hadn’t explained why you were getting redirected. From a UI design standpoint I tried to cram a lot of things into the Organizer page and doing things like trying to restrict the height and forcing page scrolling within page scrolling made the UI even harder to work with.

With Guide 5 I hope I have corrected a lot of that. Splitting up the Organizer so you now create and manage guides on one page, then having a separate page used to map out guides will hopefully help make things make more sense. I got rid of all of those redirects and even got rid of some settings being required in the first place.

Guide organizer
Guide list
Guide cms guide
Guide settings

Doing a little dogfooding, I also added my own Guide buttons around the pages in Guide where I thought documentation could be helpful. These buttons show the documentation in slideouts—of course 🎉

Guide 5 is Now Live

Guide 5 is now available and can be installed via composer or from the Craft CMS Plugin Store. The features above are available in the paid PRO edition, and a LITE edition is available for folks who just need a simple CMS Guide.

If you run into any bugs please add an issue to the Guide GitHub issue page. If you have questions or feature requests, please head over to the Guide GitHub Discussions page or drop my a line.

📓