Adding A Commenting System To A WYSIWYG Editor

Original Source: https://smashingmagazine.com/2021/05/commenting-system-wysiwyg-editor/

In recent years, we’ve seen Collaboration penetrate a lot of digital workflows and use-cases across many professions. Just within the Design and Software Engineering community, we see designers collaborate on design artifacts using tools like Figma, teams doing Sprint and Project Planning using tools like Mural and interviews being conducted using CoderPad. All these tools are constantly aiming to bridge the gap between an online and a physical world experience of executing these workflows and making the collaboration experience as rich and seamless as possible.

For the majority of the Collaboration Tools like these, the ability to share opinions with one another and have discussions about the same content is a must-have. A Commenting System that enables collaborators to annotate parts of a document and have conversations about them is at the heart of this concept. Along with building one for text in a WYSIWYG Editor, the article tries to engage the readers into how we try to weigh the pros and cons and attempt to find a balance between application complexity and user experience when it comes to building features for WYSIWYG Editors or Word Processors in general.

Representing Comments In Document Structure

In order to find a way to represent comments in a rich text document’s data structure, let’s look at a few scenarios under which comments could be created inside an editor.

Comments created over text that has no styles on it (basic scenario);
Comments created over text that may be bold/italic/underlined, and so on;
Comments that overlap each other in some way (partial overlap where two comments share only a few words or fully-contained where one comment’s text is fully contained within text of another comment);
Comments created over text inside a link (special because links are nodes themselves in our document structure);
Comments that span multiple paragraphs (special because paragraphs are nodes in our document structure and comments are applied to text nodes which are paragraph’s children).

Looking at the above use-cases, it seems like comments in the way they can come up in a rich text document are very similar to character styles (bold, italics etc). They can overlap with each other, go over text in other types of nodes like links and even span multiple parent nodes like paragraphs.

For this reason, we use the same method to represent comments as we do for character styles, i.e. “Marks” (as they are so called in SlateJS terminology). Marks are just regular properties on nodes — speciality being that Slate’s API around marks (Editor.addMark and Editor.removeMark) handles changing of the node hierarchy as multiple marks get applied to the same range of text. This is extremely useful to us as we deal with a lot of different combinations of overlapping comments.

Comment Threads As Marks

Whenever a user selects a range of text and tries to insert a comment, technically, they’re starting a new comment thread for that text range. Because we would allow them to insert a comment and later replies to that comment, we treat this event as a new comment thread insertion in the document.

The way we represent comment threads as marks is that each comment thread is represented by a mark named as commentThread_threadID where threadID is a unique ID we assign to each comment thread. So, if the same range of text has two comment threads over it, it would have two properties set to the true — commentThread_thread1 and commentThread_thread2. This is where comment threads are very similar to character styles since if the same text was bold and italic, it would have both the properties set to true — bold and italic.

Before we dive into actually setting this structure up, it’s worth looking at how the text nodes change as comment threads get applied to them. The way this works (as it does with any mark) is that when a mark property is being set on the selected text, Slate’s Editor.addMark API would split the text node(s) if needed such that in the resulting structure, text nodes are set up in a way that each text node has the exact same value of the mark.

To understand this better, take a look at the following three examples that show the before-and-after state of the text nodes once a comment thread is inserted on the selected text:

Highlighting Commented Text

Now that we know how we are going to represent comments in the document structure, let’s go ahead and add a few to the example document from the first article and configure the editor to actually show them as highlighted. Since we will have a lot of utility functions to deal with comments in this article, we create a EditorCommentUtils module that will house all these utils. To start with, we create a function that creates a mark for a given comment thread ID. We then use that to insert a few comment threads in our ExampleDocument.

# src/utils/EditorCommentUtils.js

const COMMENT_THREAD_PREFIX = “commentThread_”;

export function getMarkForCommentThreadID(threadID) {
return `${COMMENT_THREAD_PREFIX}${threadID}`;
}

Below image underlines in red the ranges of text that we have as example comment threads added in the next code snippet. Note that the text ‘Richard McClintock’ has two comment threads that overlap each other. Specifically, this is a case of one comment thread being fully contained inside another.

# src/utils/ExampleDocument.js
import { getMarkForCommentThreadID } from “../utils/EditorCommentUtils”;
import { v4 as uuid } from “uuid”;

const exampleOverlappingCommentThreadID = uuid();

const ExampleDocument = [

{
text: “Lorem ipsum”,
[getMarkForCommentThreadID(uuid())]: true,
},

{
text: “Richard McClintock”,
// note the two comment threads here.
[getMarkForCommentThreadID(uuid())]: true,
[getMarkForCommentThreadID(exampleOverlappingCommentThreadID)]: true,
},
{
text: “, a Latin scholar”,
[getMarkForCommentThreadID(exampleOverlappingCommentThreadID)]: true,
},

];

We focus on the UI side of things of a Commenting System in this article so we assign them IDs in the example document directly using the npm package uuid. Very likely that in a production version of an editor, these IDs are created by a backend service.

We now focus on tweaking the editor to show these text nodes as highlighted. In order to do that, when rendering text nodes, we need a way to tell if it has comment threads on it. We add a util getCommentThreadsOnTextNode for that. We build on the StyledText component that we created in the first article to handle the case where it may be trying to render a text node with comments on. Since we have some more functionality coming that would be added to commented text nodes later, we create a component CommentedText that renders the commented text. StyledText will check if the text node it’s trying to render has any comments on it. If it does, it renders CommentedText. It uses a util getCommentThreadsOnTextNode to deduce that.

# src/utils/EditorCommentUtils.js

export function getCommentThreadsOnTextNode(textNode) {
return new Set(
// Because marks are just properties on nodes,
// we can simply use Object.keys() here.
Object.keys(textNode)
.filter(isCommentThreadIDMark)
.map(getCommentThreadIDFromMark)
);
}

export function getCommentThreadIDFromMark(mark) {
if (!isCommentThreadIDMark(mark)) {
throw new Error(“Expected mark to be of a comment thread”);
}
return mark.replace(COMMENT_THREAD_PREFIX, “”);
}

function isCommentThreadIDMark(mayBeCommentThread) {
return mayBeCommentThread.indexOf(COMMENT_THREAD_PREFIX) === 0;
}

The first article built a component StyledText that renders text nodes (handling character styles and so on). We extend that component to use the above util and render a CommentedText component if the node has comments on it.

# src/components/StyledText.js

import { getCommentThreadsOnTextNode } from “../utils/EditorCommentUtils”;

export default function StyledText({ attributes, children, leaf }) {

const commentThreads = getCommentThreadsOnTextNode(leaf);

if (commentThreads.size > 0) {
return (
<CommentedText
{…attributes}
// We use commentThreads and textNode props later in the article.
commentThreads={commentThreads}
textNode={leaf}
>
{children}
</CommentedText>
);
}

return <span {…attributes}>{children}</span>;
}

Below is the implementation of CommentedText that renders the text node and attaches the CSS that shows it as highlighted.

# src/components/CommentedText.js

import “./CommentedText.css”;

import classNames from “classnames”;

export default function CommentedText(props) {
const { commentThreads, …otherProps } = props;
return (
<span
{…otherProps}
className={classNames({
comment: true,
})}
>
{props.children}
</span>
);
}

# src/components/CommentedText.css

.comment {
background-color: #feeab5;
}

With all of the above code coming together, we now see text nodes with comment threads highlighted in the editor.

Note: The users currently cannot tell if certain text has overlapping comments on it. The entire highlighted text range looks like a single comment thread. We address that later in the article where we introduce the concept of active comment thread which lets users select a specific comment thread and be able to see its range in the editor.

UI Storage For Comments

Before we add the functionality that enables a user to insert new comments, we first setup a UI state to hold our comment threads. In this article, we use RecoilJS as our state management library to store comment threads, comments contained inside the threads and other metadata like creation time, status, comment author etc. Let’s add Recoil to our application:

> yarn add recoil

We use Recoil atoms to store these two data structures. If you’re not familiar with Recoil, atoms are what hold the application state. For different pieces of application state, you’d usually want to set up different atoms. Atom Family is a collection of atoms — it can be thought to be a Map from a unique key identifying the atom to the atoms themselves. It’s worth going through core concepts of Recoil at this point and familiarizing ourselves with them.

For our use case, we store comment threads as an Atom family and then wrap our application in a RecoilRoot component. RecoilRoot is applied to provide the context in which the atom values are going to be used. We create a separate module CommentState that holds our Recoil atom definitions as we add more atom definitions later in the article.

# src/utils/CommentState.js

import { atom, atomFamily } from “recoil”;

export const commentThreadsState = atomFamily({
key: “commentThreads”,
default: [],
});

export const commentThreadIDsState = atom({
key: “commentThreadIDs”,
default: new Set([]),
});

Worth calling out few things about these atom definitions:

Each atom/atom family is uniquely identified by a key and can be set up with a default value.
As we build further in this article, we are going to need a way to iterate over all the comment threads which would basically mean needing a way to iterate over commentThreadsState atom family. At the time of writing this article, the way to do that with Recoil is to set up another atom that holds all the IDs of the atom family. We do that with commentThreadIDsState above. Both these atoms would have to be kept in sync whenever we add/delete comment threads.

We add a RecoilRoot wrapper in our root App component so we can use these atoms later. Recoil’s documentation also provides a helpful Debugger component that we take as it is and drop into our editor. This component will leave console.debug logs to our Dev console as Recoil atoms are updated in real-time.

# src/components/App.js

import { RecoilRoot } from “recoil”;

export default function App() {

return (
<RecoilRoot>
>

<Editor document={document} onChange={updateDocument} />

</RecoilRoot>
);
}

# src/components/Editor.js

export default function Editor({ … }): JSX.Element {
…..

return (
<>
<Slate>
…..
</Slate>
<DebugObserver />
</>
);

function DebugObserver(): React.Node {
// see API link above for implementation.
}

We also need to need to add code that initializes our atoms with the comment threads that already exist on the document (the ones we added to our example document in the previous section, for instance). We do that at a later point when we build the Comments Sidebar that needs to read all the comment threads in a document.

At this point, we load our application, make sure there are no errors pointing to our Recoil setup and move forward.

Adding New Comments

In this section, we add a button to the toolbar that lets the user add comments (viz. create a new comment thread) for the selected text range. When the user selects a text range and clicks on this button, we need to do the below:

Assign a unique ID to the new comment thread being inserted.
Add a new mark to Slate document structure with the ID so the user sees that text highlighted.
Add the new comment thread to Recoil atoms we created in the previous section.

Let’s add a util function to EditorCommentUtils that does #1 and #2.

# src/utils/EditorCommentUtils.js

import { Editor } from “slate”;
import { v4 as uuidv4 } from “uuid”;

export function insertCommentThread(editor, addCommentThreadToState) {
const threadID = uuidv4();
const newCommentThread = {
// comments as added would be appended to the thread here.
comments: [],
creationTime: new Date(),
// Newly created comment threads are OPEN. We deal with statuses
// later in the article.
status: “open”,
};
addCommentThreadToState(threadID, newCommentThread);
Editor.addMark(editor, getMarkForCommentThreadID(threadID), true);
return threadID;
}

By using the concept of marks to store each comment thread as its own mark, we’re able to simply use the Editor.addMark API to add a new comment thread on the text range selected. This call alone handles all the different cases of adding comments — some of which we described in the earlier section — partially overlapping comments, comments inside/overlapping links, comments over bold/italic text, comments spanning paragraphs and so on. This API call adjusts the node hierarchy to create as many new text nodes as needed to handle these cases.

addCommentThreadToState is a callback function that handles step #3 — adding the new comment thread to Recoil atom . We implement that next as a custom callback hook so that it’s re-usable. This callback needs to add the new comment thread to both the atoms — commentThreadsState and commentThreadIDsState. To be able to do this, we use the useRecoilCallback hook. This hook can be used to construct a callback which gets a few things that can be used to read/set atom data. The one we’re interested in right now is the set function which can be used to update an atom value as set(atom, newValueOrUpdaterFunction).

# src/hooks/useAddCommentThreadToState.js

import {
commentThreadIDsState,
commentThreadsState,
} from “../utils/CommentState”;

import { useRecoilCallback } from “recoil”;

export default function useAddCommentThreadToState() {
return useRecoilCallback(
({ set }) => (id, threadData) => {
set(commentThreadIDsState, (ids) => new Set([…Array.from(ids), id]));
set(commentThreadsState(id), threadData);
},
[]
);
}

The first call to set adds the new ID to the existing set of comment thread IDs and returns the new Set(which becomes the new value of the atom).

In the second call, we get the atom for the ID from the atom family — commentThreadsState as commentThreadsState(id) and then set the threadData to be its value. atomFamilyName(atomID) is how Recoil lets us access an atom from its atom family using the unique key. Loosely speaking, we could say that if commentThreadsState was a javascript Map, this call is basically — commentThreadsState.set(id, threadData).

Now that we have all this code setup to handle insertion of a new comment thread to the document and Recoil atoms, lets add a button to our toolbar and wire it up with the call to these functions.

# src/components/Toolbar.js

import { insertCommentThread } from “../utils/EditorCommentUtils”;
import useAddCommentThreadToState from “../hooks/useAddCommentThreadToState”;

export default function Toolbar({ selection, previousSelection }) {
const editor = useEditor();

const addCommentThread = useAddCommentThreadToState();

const onInsertComment = useCallback(() => {
const newCommentThreadID = insertCommentThread(editor, addCommentThread);
}, [editor, addCommentThread]);

return (
<div className=”toolbar”>

<ToolBarButton
isActive={false}
label={<i className={bi ${getIconForButton(“comment”)}} />}
onMouseDown={onInsertComment}
/>
</div>
);
}

Note: We use onMouseDown and not onClick which would have made the editor lose focus and selection to become null. We’ve discussed that in a little more detail in the link insertion section of the first article.

In the below example, we see the insertion in action for a simple comment thread and an overlapping comment thread with links. Notice how we get updates from Recoil Debugger confirming our state is getting updated correctly. We also verify that new text nodes are created as threads are being added to the document.

In the above example, the user inserts the following comment threads in that order:

Comment Thread #1 over character ‘B’ (length = 1).
Comment Thread #2 over ‘AB’ (length = 2).
Comment Thread #3 over ‘BC’ (length = 2).

At the end of these insertions, because of the way Slate splits the text nodes with marks, we will have three text nodes — one for each character. Now, if the user clicks on ‘B’, going by the shortest length rule, we select thread #1 as it is the shortest of the three in length. If we don’t do that, we wouldn’t have a way to select Comment Thread #1 ever since it is only one-character in length and also a part of two other threads.

Although this rule makes it easy to surface shorter-length comment threads, we could run into situations where longer comment threads become inaccessible since all the characters contained in them are part of some other shorter comment thread. Let’s look at an example for that.

Let’s assume we have 100 characters (say, character ‘A’ typed 100 times that is) and the user inserts comment threads in the following order:

Comment Thread # 1 of range 20,80
Comment Thread # 2 of range 0,50
Comment Thread # 3 of range 51,100

As you can see in the above example, if we follow the rule we just described here, clicking on any character between #20 and #80, would always select threads #2 or #3 since they are shorter than #1 and hence #1 would not be selectable. Another scenario where this rule can leave us undecided as to which comment thread to select is when there are more than one comment threads of the same shortest length on a text node.

For such combination of overlapping comments and many other such combinations that one could think of where following this rule makes a certain comment thread inaccessible by clicking on text, we build a Comments Sidebar later in this article which gives user a view of all the comment threads present in the document so they can click on those threads in the sidebar and activate them in the editor to see the range of the comment. We still would want to have this rule and implement it as it should cover a lot of overlap scenarios except for the less-likely examples we cited above. We put in all this effort around this rule primarily because seeing highlighted text in the editor and clicking on it to comment is a more intuitive way of accessing a comment on text than merely using a list of comments in the sidebar.

Insertion Rule

The rule is:

“If the text user has selected and is trying to comment on is already fully covered by comment thread(s), don’t allow that insertion.”

This is so because if we did allow this insertion, each character in that range would end up having at least two comment threads (one existing and another the new one we just allowed) making it difficult for us to determine which one to select when the user clicks on that character later.

Looking at this rule, one might wonder why we need it in the first place if we already have the Shortest Comment Range Rule that allows us to select the smallest text range. Why not allow all combinations of overlaps if we can use the first rule to deduce the right comment thread to show? As some of the examples we’ve discussed earlier, the first rule works for a lot of scenarios but not all of them. With the Insertion Rule, we try to minimize the number of scenarios where the first rule cannot help us and we have to fallback on the Sidebar as the only way for the user to access that comment thread. Insertion Rule also prevents exact-overlaps of comment threads. This rule is commonly implemented by a lot of popular editors.

Below is an example where if this rule didn’t exist, we would allow the Comment Thread #3 and then as a result of the first rule, #3 would not be accessible since it would become the longest in length.

In this example, let’s assume we don’t wait for intersection to become 0 and just stop when we reach the edge of a comment thread. Now, if the user clicked on #2 and we start traversal in reverse direction, we’d stop at the start of text node #2 itself since that’s the start of the comment thread A. As a result, we might not compute the comment thread lengths correctly for A & B. With the implementation above traversing the farthest edges (text nodes 1,2, and 3), we should get B as the shortest comment thread as expected.

To see the implementation visually, below is a walkthrough with a slideshow of the iterations. We have two comment threads A and B that overlap each other over text node #3 and the user clicks on the overlapping text node #3.

Now that we have all the code in to make selection of comment threads work, let’s see it in action. To test our traversal code well, we test some straightforward cases of overlap and some edge cases like:

Clicking on a commented text node at the start/end of the editor.
Clicking on a commented text node with comment threads spanning multiple paragraphs.
Clicking on a commented text node right before an image node.
Clicking on a commented text node overlapping links.

Now that our state is correctly initialized, we can start implementing the sidebar. All our comment threads in the UI are stored in the Recoil atom family — commentThreadsState. As highlighted earlier, the way we iterate through all the items in a Recoil atom family is by tracking the atom keys/ids in another atom. We’ve been doing that with commentThreadIDsState. Let’s add the CommentSidebar component that iterates through the set of ids in this atom and renders a CommentThread component for each.

# src/components/CommentsSidebar.js

import “./CommentSidebar.css”;

import {commentThreadIDsState,} from “../utils/CommentState”;
import { useRecoilValue } from “recoil”;

export default function CommentsSidebar(params) {
const allCommentThreadIDs = useRecoilValue(commentThreadIDsState);

return (
<Card className={“comments-sidebar”}>
<Card.Header>Comments</Card.Header>
<Card.Body>
{Array.from(allCommentThreadIDs).map((id) => (
<Row key={id}>
<Col>
<CommentThread id={id} />
</Col>
</Row>
))}
</Card.Body>
</Card>
);
}

Now, we implement the CommentThread component that listens to the Recoil atom in the family corresponding to the comment thread it is rendering. This way, as the user adds more comments on the thread in the editor or changes any other metadata, we can update the sidebar to reflect that.

As the sidebar could grow to be really big for a document with a lot of comments, we hide all comments but the first one when we render the sidebar. The user can use the ‘Show/Hide Replies’ button to show/hide the entire thread of comments.

# src/components/CommentSidebar.js

function CommentThread({ id }) {
const { comments } = useRecoilValue(commentThreadsState(id));

const [shouldShowReplies, setShouldShowReplies] = useState(false);
const onBtnClick = useCallback(() => {
setShouldShowReplies(!shouldShowReplies);
}, [shouldShowReplies, setShouldShowReplies]);

if (comments.length === 0) {
return null;
}

const [firstComment, …otherComments] = comments;
return (
<Card
body={true}
className={classNames({
“comment-thread-container”: true,
})}
>
<CommentRow comment={firstComment} showConnector={false} />
{shouldShowReplies
? otherComments.map((comment, index) => (
<CommentRow key={comment-${index}} comment={comment} showConnector={true} />
))
: null}
{comments.length > 1 ? (
<Button
className={“show-replies-btn”}
size=”sm”
variant=”outline-primary”
onClick={onBtnClick}
>
{shouldShowReplies ? “Hide Replies” : “Show Replies”}
</Button>
) : null}
</Card>
);
}

We’ve reused the CommentRow component from the popover although we added a design treatment using showConnector prop that basically makes all the comments look connected with a thread in the sidebar.

Now, we render the CommentSidebar in the Editor and verify that it shows all the threads we have in the document and correctly updates as we add new threads or new comments to existing threads.

# src/components/Editor.js

return (
<>
<Slate … >
…..
<div className={“sidebar-wrapper”}>
<CommentsSidebar />
</div>
</Slate>
</>
);

We now move on to implementing a popular Comments Sidebar interaction found in editors:

Clicking on a comment thread in the sidebar should select/activate that comment thread. We also add a differential design treatment to highlight a comment thread in the sidebar if it’s active in the editor. To be able to do so, we use the Recoil atom — activeCommentThreadIDAtom. Let’s update the CommentThread component to support this.

# src/components/CommentsSidebar.js

function CommentThread({ id }) {

const [activeCommentThreadID, setActiveCommentThreadID] = useRecoilState(
activeCommentThreadIDAtom
);

const onClick = useCallback(() => {
setActiveCommentThreadID(id);
}, [id, setActiveCommentThreadID]);

return (
<Card
body={true}
className={classNames({
“comment-thread-container”: true,
“is-active”: activeCommentThreadID === id,
})}
onClick={onClick}
>
….
</Card>
);

If we look closely, we have a bug in our implementation of sync-ing the active comment thread with the sidebar. As we click on different comment threads in the sidebar, the correct comment thread is indeed highlighted in the editor. However, the Comment Popover doesn’t actually move to the changed active comment thread. It stays where it was first rendered. If we look at the implementation of the Comment Popover, it renders itself against the first text node in the editor’s selection. At that point in the implementation, the only way to select a comment thread was to click on a text node so we could conveniently rely on the editor’s selection since it was updated by Slate as a result of the click event. In the above onClick event, we don’t update the selection but merely update the Recoil atom value causing Slate’s selection to remain unchanged and hence the Comment Popover doesn’t move.

A solution to this problem is to update the editor’s selection along with updating the Recoil atom when the user clicks on the comment thread in the sidebar. The steps do this are:

Find all text nodes that have this comment thread on them that we are going to set as the new active thread.
Sort these text nodes in the order in which they appear in the document (We use Slate’s Path.compare API for this).
Compute a selection range that spans from the start of the first text node to the end of the last text node.
Set the selection range to be the editor’s new selection (using Slate’s Transforms.select API).

If we just wanted to fix the bug, we could just find the first text node in Step #1 that has the comment thread and set that to be the editor’s selection. However, it feels like a cleaner approach to select the entire comment range as we really are selecting the comment thread.

Let’s update the onClick callback implementation to include the steps above.

const onClick = useCallback(() => {

const textNodesWithThread = Editor.nodes(editor, {
at: [],
mode: “lowest”,
match: (n) => Text.isText(n) && getCommentThreadsOnTextNode(n).has(id),
});

let textNodeEntry = textNodesWithThread.next().value;
const allTextNodePaths = [];

while (textNodeEntry != null) {
allTextNodePaths.push(textNodeEntry[1]);
textNodeEntry = textNodesWithThread.next().value;
}

// sort the text nodes
allTextNodePaths.sort((p1, p2) => Path.compare(p1, p2));

// set the selection on the editor
Transforms.select(editor, {
anchor: Editor.point(editor, allTextNodePaths[0], { edge: “start” }),
focus: Editor.point(
editor,
allTextNodePaths[allTextNodePaths.length – 1],
{ edge: “end” }
),
});

// Update the Recoil atom value.
setActiveCommentThreadID(id);
}, [editor, id, setActiveCommentThreadID]);

Note: allTextNodePaths contains the path to all the text nodes. We use the Editor.point API to get the start and end points at that path. The first article goes through Slate’s Location concepts. They’re also well-documented on Slate’s documentation.

Let’s verify that this implementation does fix the bug and the Comment Popover moves to the active comment thread correctly. This time, we also test with a case of overlapping threads to make sure it doesn’t break there.

With the bug fix, we’ve enabled another sidebar interaction that we haven’t discussed yet. If we have a really long document and the user clicks on a comment thread in the sidebar that’s outside the viewport, we’d want to scroll to that part of the document so the user can focus on the comment thread in the editor. By setting the selection above using Slate’s API, we get that for free. Let’s see it in action below.

With that, we wrap our implementation of the sidebar. Towards the end of the article, we list out some nice feature additions and enhancements we can do to the Comments Sidebar that help elevate the Commenting and Review experience on the editor.

Resolving And Re-Opening Comments

In this section, we focus on enabling users to mark comment threads as ‘Resolved’ or be able to re-open them for discussion if needed. From an implementation detail perspective, this is the status metadata on a comment thread that we change as the user performs this action. From a user’s perspective, this is a very useful feature as it gives them a way to affirm that the discussion about something on the document has concluded or needs to be re-opened because there are some updates/new perspectives, and so on.

To enable toggling the status, we add a button to the CommentPopover that allows the user to toggle between the two statuses: open and resolved.

# src/components/CommentThreadPopover.js

export default function CommentThreadPopover({
editorOffsets,
selection,
threadID,
}) {

const [threadData, setCommentThreadData] = useRecoilState(
commentThreadsState(threadID)
);

const onToggleStatus = useCallback(() => {
const currentStatus = threadData.status;
setCommentThreadData((threadData) => ({
…threadData,
status: currentStatus === “open” ? “resolved” : “open”,
}));
}, [setCommentThreadData, threadData.status]);

return (
<NodePopover

header={
<Header
status={threadData.status}
shouldAllowStatusChange={threadData.comments.length > 0}
onToggleStatus={onToggleStatus}
/>
}
>
<div className={“comment-list”}>

</div>
</NodePopover>
);
}

function Header({ onToggleStatus, shouldAllowStatusChange, status }) {
return (
<div className={“comment-thread-popover-header”}>
{shouldAllowStatusChange && status != null ? (
<Button size=”sm” variant=”primary” onClick={onToggleStatus}>
{status === “open” ? “Resolve” : “Re-Open”}
</Button>
) : null}
</div>
);
}

Before we test this, let’s also give the Comments Sidebar a differential design treatment for resolved comments so that the user can easily detect which comment threads are un-resolved or open and focus on those if they want to.

# src/components/CommentsSidebar.js

function CommentThread({ id }) {

const { comments, status } = useRecoilValue(commentThreadsState(id));


return (
<Card
body={true}
className={classNames({
“comment-thread-container”: true,
“is-resolved”: status === “resolved”,
“is-active”: activeCommentThreadID === id,
})}
onClick={onClick}
>

</Card>
);
}

Conclusion

In this article, we built the core UI infrastructure for a Commenting System on a Rich Text Editor. The set of functionalities we add here act as a foundation to build a richer Collaboration Experience on an editor where collaborators could annotate parts of the document and have conversations about them. Adding a Comments Sidebar gives us a space to have more conversational or review-based functionalities to be enabled on the product.

Along those lines, here are some of features that a Rich Text Editor could consider adding on top of what we built in this article:

Support for @ mentions so collaborators could tag one another in comments;
Support for media types like images and videos to be added to comment threads;
Suggestion Mode at the document level that allows reviewers to make edits to the document that appear as suggestions for changes. One could refer to this feature in Google Docs or Change Tracking in Microsoft Word as examples;
Enhancements to the sidebar to search conversations by keyword, filter threads by status or comment author(s), and so on.

How To Build And Launch Responsive Websites Faster With Editor X

Original Source: https://smashingmagazine.com/2021/05/build-launch-responsive-websites-editorx/

As designers, we are used to having a lot of creative freedom within our tools. We intuitively select, move and fine-tune things until they look just right. Once the work leaves the design tool, we give away this level of control to an unpredictable, diverse, and fluid browser environment. There, some of our decisions suddenly will need to be refined, and as we want to introduce changes, we need to dive into code. Or explain these changes clearly and unambiguously, to avoid misunderstandings down the line. The latter part can be frustrating for all parties involved.

While web builders have been around for a long time, it wasn’t until recently that they became practical for professional use. Closing the gap between design and code has become the north star for many companies, and often this issue is seen as the most critical pain point that every team attempts to solve in their own way.

In this article, we’ll look into Editor X, a sophisticated platform for professionals and agencies to build websites, driven by an ambitious goal to close the gap for good.

What’s Editor X?

Chances are high that you’ve stumbled upon web builders in the past — often with a grain of skepticism and doubt about the outcome of these tools. Many of such builders heavily rely on pre-made templates with some level of customization. Editor X goes far beyond that by providing a platform for professional designers and agencies to create web experiences with a wide variety of flexible components and a series of advanced features.

The best way to find out what Editor X is capable of would be to build something with it and in this article, we’ll create a website from scratch.

Editor X follows well-established patterns and anyone with design experience will feel comfortable with it within a few minutes. For the most part, we’ll repeat the same workflow of adding elements, moving them across the canvas, and adjusting their properties.

On the top left side, we have toggles for panels that will help us add elements, navigate layers and manage pages. Then at the center of our workspace is the canvas, where we’ll directly interact with the design of the page. You’ll notice that the canvas is also resizable, which allows us to easily experiment with different viewports. Whenever we select anything from the canvas, we’ll see the Inspector panel open on the right.

The earlier the entire team is involved in the conversation about a new design, the more issues can be resolved early. Often you’d need to take a screenshot and paste it on Slack, or use another tool to discuss a design via a clickable prototype. On Editor X, you can invite teammates to the project, and assign them individual roles and permissions. There’s also an option to communicate with your team in real-time by leaving comments on the page or on specific components.

Creating The Structure Of The Website

Before we start adding content, we’ll create sections that will serve as a skeleton for our page. Sections in Editor X are essentially large containers that hold our content. As soon as you create a new page, you’ll see a header and footer section already added to the canvas. To add new sections we can click on any existing one and we’ll see a blue “+” icon at the edge of it.

Adding Content And Styling Our Page

Adding elements in Editor X is straightforward. We open the “Add” panel and drag elements into the canvas. Within that panel, we have a wide range of elements, components and entire sections that will become the building blocks for our website.

Every element that we drop on the canvas can be easily moved and aligned. We can also control how elements react to changes in the screen size by using the “Docking” feature. When the screen is being resized, docking options will determine the vertical and horizontal position of elements within different types of containers.

For the right side of our section, we’ll add an image that we’ll replace with our illustration. To make this work, we just need to click on “Change image” and then upload our assets to the media library. You’ll notice that apart from the assets we’ve added, you have direct access to a large library of free stock photos and pre-designed illustrations.

To implement the three steps of our “How it works” section, we’ll use a repeater element with three items and 20px space in between. The repeater is essentially a list of items where the style and layout of the first item are repeated automatically for the rest while the content can be different.

First, we’ll add the title and paragraph within the first item and see them repeat in realtime. Above them, we’ll add a container with a border and a text element inside the container by going to Quick add → Container → Inspector → Design → Corners.

Now that we have the content of our header, it’s time to start applying some styles to it. We could go the usual route and apply styles element by element, but we can also use the “Theme manager” to create global typography and color styles that will automatically define these changes everywhere. This goes beyond the scope of our page along, so we can use it to match the style across our entire site.

Click on the theme manager icon on the top bar of the editor. From there, we can manage the global text and color styles on the site. We’ll start by changing the background color to #030F1D and the color of our action items to #030F1D. Then we’ll change the headline fonts to Sora and also adjust the typography colors to fit our palette.

This concept goes even further as we can save our themes to a design library that can be used across all the websites we create with the tool. This makes it easy for teams to implement and manage their design systems. Also, imagine working on a series of themes and designs that you might want to reuse across a wide range of your products, or if you want to maintain a series of products for your clients. This could save quite an amount of time — and managed from one central place.

The next section will serve as a showcase of the product. First, we’ll add a headline, sub-headline, and an image element to the canvas and turn them into a stack. Then we’ll dock them to the center and increase the height of the section.

To achieve the overlapping effect we’ll add the particles in two separate image elements and arrange them to appear in the back.

Lastly, we’ll update the colors to match our palette, for the background we’d use #FFECE4, while the color of the sub-headline will be #836153.

Forms are essential for most websites and in our case, we’ll need one to collect the contact information of visitors interested in our product.

To create a form we’ll need to go to the Add panel and select “Contact & Forms”, from there we can see a variety of templates that we can use as a starting point. For our page, we will choose the “contact form” by dragging it to the canvas.

In the last step, we’ll add a menu to our website. With the tool, we can create complex websites with many pages tied together by seamless navigation, but in our case, we only need to navigate between the sections of this specific page. For this purpose, we’ll use a feature called “Anchors”. We’ll go over the sections that will be part of our menu and we’ll add an anchor that we’ll later use in the menu settings.

Select any element, then click the “Anchor” section in the Inspector panel on the right side of the editor. Then click on the toggle and simply name the anchor. We’ll repeat this for all sections we’d like to have in the navigation.

Now to add those in the menu, click “Manage menu” and then “Add link”. From there. we need to select the Anchor option and the anchor we want to link.

Making The Site Come To Life

One way to make the site more interactive and distinctive is to add animations to our elements. Of course, we can add animation on the platform as well, and apply it to any element or section on the canvas. To achieve that, we need to select the elements we want to animate, and then click on the Animation icon.

There are plenty of presets that we can use out of the box, but there’s also the option to fine-tune variables such as duration and delay.

In our case, we’d like to add a subtle fade-in animation to all of the headlines and images on the canvas.

Animation showcase (Large preview)

Designing For Different Screen Sizes

It’s common to see mock-ups created for desktop first, or for mobile-first, but in practice, we actually need to be creating both at the same time. The priorities that we define for our content blocks might need to change from one screen size to another, but we need to explore how we can put the right emphasis on the right elements, and choose the right way to position them both on desktop and on mobile. With the tool, we can achieve that by designing for individual breakpoints and using fluid and relative size units of measure.

Obviously, it’s a good idea to add breakpoints only when we need them, so we can add our custom breakpoints as we are previewing the site growing from small to large viewports. Obviously, we can do that without leaving the tool. Whenever we need a breakpoint, we can add them (or edit already existing ones) by clicking on the three-dotted menu next to the breakpoints.

If you’ve used relative sizes till that point, many of the elements will already resize properly. For the rest, we’ll go through the different breakpoints and create design overrides to make sure that everything looks as expected. The changes that we make will be applied to the specific breakpoint range that we’ve selected, and they will cascade down, too.

It’s important to note that we don’t have to rely on the pre-made building blocks alone though. If you need to build in a complex functionality for your projects, you can do it as well. In fact, you can add your own JavaScript, connect to APIs, use npm packages and automate client-to-server interactions with web modules. These features are available via an integrated development platform called Velo.

But for the scope of this article, although we mostly combined a few elements without rewriting or fixing code, the results are quite solid compared to what one would normally expect from a website builder. Overall, the score is quite high on performance and accessibility, especially on desktop, although you might need to optimize your site more for mobile devices.

Wrapping Up

When it comes to web builders, it’s not unusual to be disappointed by the outcome — with plenty of accessibility and performance issues, along with bulky and messy markup, overly specific CSS, and slow JavaScript. When we look into the website creation process on Editor X, it appears to be a platform that has gone quite far to provide a straightforward environment for building good websites, while also including collaboration features, responsive testing, and some components that might need quite a bit of time to prototype or set up otherwise.

If you are working with agencies or organizations where you plan to reuse components, or if you need to set up and maintain sites quickly for a variety of your clients, Editor X could be an interesting option worth considering. It comes along with personal and business plans, support for online payments, eCommerce, domains and storage, online bookings, ticket and event management, as well as video monetization. Chances are high that you’ll find what you need — both for quick prototypes and extensive client work.

You can create an Editor X account for free and test all features, without any strings attached.

Useful VS Code Extensions For Web Developers

Original Source: https://smashingmagazine.com/2021/05/useful-vs-code-extensions-web-developers/

We spend so much time in our text editors, and every now and again we encounter those little frustrating issues that slow us down. Perhaps finding the right file takes too long, or the navigation between files is tad too difficult, or finding a matching closing bracket becomes a long-winded adventure on its own. Let’s fix all these annoyances for good. In this post, we look into useful VS Code extensions for front-end development, from productivity boosters to debugging helpers.

Table of Contents

Below you’ll find quick jumps to specific extensions that you might need. Scroll down for a general overview. Or skip the table of contents.

automate log messages
bundle size
code formatting
code screenshots
comments
debugging
DevTools
file utils
file tags and labels
folder icons
fonts for coding
Git
Git history
highlight annotations
highlight brackets and tags
highlight harmful characters
highlight indents
highlight stacking contexts
highlight workspaces
IntelliCode
onboarding
open GitHub fast
pets
rapid JS/TS prototyping
remote SSH access
Sass compilation
tips and tricks

Automating Log Messages

When it comes to log messages, the turbo-console-log extension has got your back. It automates the operation of writing meaningful log messages and inserts them automatically.

All you need to do is select the variable which you want to debug, press Ctrl + Alt + L, and the log message will be inserted in the next line. Keyboard shortcuts let you comment, uncomment, or delete all log messages from the current document.

Keeping Bundle Size Under Control

We all know that performance matters, but in practice, it can be quite a challenge not to lose it out of sight when you’re in the flow of writing code. To keep your bundle size under control, the Import Cost extension lets you immediately know if you’re importing a hefty package into your project.

Import Cost isn’t a bundle analysis tool but was built with the idea to help you find possible performance bottlenecks before you ship them to your users. To do so, it gives you instant feedback by displaying the size of an imported third-party library as you’re importing it, right next to your line of code. A handy little helper.

Code Formatting, Automated

When writing code, a lot of time goes into formatting. Prettier automates the task for you. It removes all original styling and ensures that the outputted code conforms to a consistent style.

Prettier parses your code and re-prints it with its own rules, taking the maximum line length into account and wrapping the code when necessary. You decide if you want to apply it to all languages or alternatively you can define the ones you prefer to format manually. Also a great solution for teams who struggle finding a common style guide.

Code Screenshots, The Fancy Way

Let’s be honest, taking good-looking screenshots of code can be a challenge. Polacode is here to change that.

Described as Polaroid for your code, Polacode lets you take and edit screenshots of your code directly in VS Code. You can resize the code’s container by dragging the corner and use commands to control the image appearance. A great solution to make the code you’ve spent so many hours on shine in the best light — in blog posts or presentations, for example.

Human-Friendly Comments

How do you handle comments? If your code requires a lot of explanations, it might be a good idea to make those usually grayed-out comments more human-friendly, so that it’s easier to see at a glance if a comment alerts you of a deprecated method, for example, or if it’s a todo your teammate left for you.

The VS Code extension Better Comments helps you do just that, categorizing annotations into alerts, queries, todos, highlights, and more. Commented-out code can also be styled to make it clear it shouldn’t be there.

Chrome Debugging Inside VS Code

Do you use Chrome and find yourself switching back and forth between the browser and your editor when debugging? Then you might want to give the VS Code Chrome debugger a try. It helps you debug client-side JavaScript code that runs in Chrome directly from VS Code.

The debugger connects to Chrome over its Chrome Debugger protocol where it maps files loaded in the browser to the files you have open in VS Code. So without leaving the editor, you can set breakpoints in your source code, set up variables to watch, and see the full call stack when debugging. A little tool to make your debugging routine more straightforward.

DevTools For VSCode Extension

Wouldn’t it be cool to have DevTools integrated into your code editor so that you don’t need to switch back and forth between the two? If you’re using VSCode and Edge, a small extension makes it possible.

The extension shows the browser’s Elements and Network tool inside VSCode, giving you the ability to see the runtime HTML structure, alter styling and layout, perform diagnostics, and debug your project — without leaving the editor. By the way, Rachel Weil shared some handy DevTools tips for working with Chromium-based browsers like Edge and Chrome at Smashing Conf San Francisco a few weeks ago. Be sure to tune into the recording to take your DevTools skills to the next level.

File Management Utils for VS Code

A lot of time is usually spent on organizing and managing files. File Utils makes the task more convenient.

The extension enables you to create, duplicate, move, rename, and delete files and directories with just a handful of commands. It also supports brace extension which automatically generates arbitrary strings strings to set up your document structure.

Adding Tags To Files In Your Editor

In large projects, finding one specific variant of a component, or just the right file requires you to know the file that you are actually looking for. But what if you could add bookmarks or labels to specific files, so you could find them faster?

File Ops VS Code Extension allows you to tag and alias files, and then quickly switch between them. You can also quickly list all tags just in case you lose track of them, view all files from the current directory and switch between .css and .js files in the same folder. You can also take a look at the video explaining how it all works. Now that will come in handy!

Folder Icons In VS Code

Custom file and folder icons in VS Code? Yes, please! To help you maneuver your workspace more easily, even if a lot of files and folder are involved, the VS Code Icons Team released an extension that brings icons to your editor. From “access” to “zip”, “Android” to “www”, the collection is sure to have the file and folder icons you need.

The project-specific icons toggle feature and project auto-detection will automatically detect the type of project you have opened in your workspace and prompt you to toggle the icons accordingly. It’s also possible to use custom icons, if you prefer.

Monospaced Fonts For Coding

Programming fonts are certainly the workhorses in typography. They need to offer great readability, enable quick text scanning, and prevent eye strain even when a developer looks at the code for hours. To help you find a programming font that meets your needs, Chris Coyier curates Coding Fonts, a selection of more than 30 (mostly free) monospaced fonts that all match this criteria.

To make the decision easier, each font comes with a short description, an overview of all characters, and HTML, CSS, and JavaScript code examples in both day and night mode. Mostafa Gaafar maintains a similar list of fonts for developers with the option to also view the code examples in different color schemes. To add custom fonts to VS Code, you’ll need to define the font in “Settings”.

Git Supercharged

A useful extension to supercharge the Git capabilities built into VS Code is GitLens. To better understand the code you’re working on, GitLens lets you glimpse into whom, why, and when a line or code block was changed.

The extension visualizes code authorships at a glance, helps you seamlessly navigate and explore Git repositories, gain valuable insights via comparison commands, and more. Everything you need to know about your codebase right at your fingertips, without leaving the editor.

Git History In VS Code

Viewing and searching git log along with the graph and details, viewing a previous copy of the file you’re working on, searching the history, comparing branches and commits — these are just a few of the features that the Git History extension offers to streamline your workflow.

Speaking of Git: Another VS Code extension worth taking a closer look at when working with Git is Git Graph: It lets you view a Git graph of your repository and easily perform Git actions from the graph.

Highlight Annotations In Your Code

Do you sometimes forget to review the to-dos you’ve added while coding? The TODO Highlight extension reminds you that there are notes or things that need your attention before you publish to production.

The keywords TODO and FIXME are preconfigured, but you can customize the configuration to your liking if you prefer. A command highlights the open comments for you right in your code or as a list of all annotations. A great little reminder.

Highlighting Matching Brackets And Tags

An intense coding session strains the eyes, so anything that helps cater for more visual clarity is a welcome helper. To take your syntax highlighting to the next level when working with VS Code, you might want to check out the Bracket Pair Colorizer. The extension identifies matching brackets — in colors you define.

Now that you’ve got full control over your brackets, another little detail to watch out for are matching opening and closing tags. VS Code does already come with a tag matching feature, but it is rather basic. The Highlight Matching Tag extension does the work more thoroughly, matching tags anywhere — from tag attributes to inside strings — and even highlighting the path from tag to tag in the status bar. Extensive styling options let you customize how tags are highlighted. HTML and JSX are officially supported.

Revealing Harmful Characters

Zero-width spaces and non-joiners, non-breaking spaces, left and right double quotation marks — when coding, some characters can be harmful because they are invisible or looking like legitimate ones. Gremlins Tracker finds them for you.

Gremlins Tracker uses a color scheme to alert you of harmful, potentially harmful, and less harmful characters. Lines that include such a character are marked with a Gremlins icon, and moving the cursor over the character gives you a hint of the potential issue. If you like, you can add new gremlins characters or override them for a specific language.

Highlighting Indentation

Indentation is key to ensure your code can be scanned quickly. A handy little plugin that makes indentations even more readable is Indent-Rainbow. It colorizes the indentation in front of your text alternating four different colors on each step and marking those lines where the indentation is not a multiple of the tab size.

While error highlighting is useful, there are instances where it might get in your way. When dealing with RegEx patterns, for example. Luckily, Indent-Rainbow lets you turn off error highlighting on those, just like on comment lines, and, if you like, you can even skip it for entire languages.

Visualizing Stacking Contexts

Do you have difficulties spotting stacking contexts when using z-index? You’re not alone! If you sometimes find yourself setting a z-index to a billion on an element and it’s not moving forward in your stacking order, CSS Stacking Contexts is for you.

The extension makes stacking contexts visible in CSS and SCSS so that you can confidently use small values when writing z-index declarations. Additionally, it will also tell you when a z-index declaration has no effect and offer quick fixes.

Custom Colors To Tell Workspaces Apart

If you frequently have multiple VS Code instances open and struggle to tell them apart, Peacock might be worth taking a closer look at: The extension subtly changes the color of your workspace.

But it’s not only when working on multiple projects at once where Peacock shines. It also comes in handy when using VS Live Share or VS Code’s Remote features and you quickly want to identify your editor.

IntelliSense: AI-Assisted Development Features

The IntelliCode extension provides AI-assisted development features for Python, TypeScript/JavaScript and Java developers in Visual Studio Code, with insights based on understanding your code context combined with machine learning.

Providing AI-assisted IntelliSense, the extension shows you recommended completion items for your code context at the top of the completions list. When it comes to overloads, it doesn’t cycle through the alphabetical list of member but presents you the most relevant one first. No more hunting through the list yourself.

Recording Guided Onboarding For Your Codebase

Large codebases can feel intimidating. CodeTour attempts to change that. The extension allows you to record and play back guided walkthroughs of your codebases, directly within the editor. Think of it as a table of contents that makes it easier to onboard or re-board to new project or feature area, to visualize bug reports, or understand the context of a code review.

To create a code tour, you can annotate lines of code (Markdown is supported) and navigate as many files as you need, and the recorder captures the sequence. The tours can be checked into a repo or exported to a “tour” file so that anyone can replay it without having to clone any code. Handy!

From GitHub To VS Code, In One Second

Once you’ve discovered a snippet of code on GitHub, what if you want to start working with it in your project immediately? Instead of cloning the repo and finding that file that you need, you can use Github1s. Just add 1s after github in the URL, press Enter, and the repo, or a single file, will open straight in VS Code.

You can also use a bookmarklet to quickly switch between github.com and github1s.com, access private repositories and there are plenty of browser extensions that are listed on the project page as well. If you need an alternative, Gitpod is a slightly more advanced option, which also allows you to start an online development environment, run parallel workspaces and work on the codebase collaboratively. (vf)

Pets For Your VS Code

Ever wanted to pep up your VS code editor? Well, how about adding a cat, dog, snake, rubber duck or even good ol’ Clippy? All you need to do is install vscode-pets and run the vscode-pets.start command in order to see the panel. Once you’ve chosen a pet, its fur color and size, lean back and watch them interact with you!

From throwing a ball and playing catch with your pet (run vscode-pets.throw-ball) to adding additional pets (run vscode-pets.spawn-pet), you’re coding workflow is bound to be anything but boring! The creator, Anthony Shaw, is open for ideas and discussion and welcomes feedback anytime.

Speed Up JavaScript / TypeScript Prototyping

If you’re looking for a way to speed up your JavaScript prototyping process, Quokka is for you. The rapid prototyping playground lives in your editor and gives prototyping, learning, and testing JavaScript and TypeScript a speed boost.

Runtime values are updated and displayed in your IDE next to your code, as you type. To get you up and running right away, there’s no config required, all you need to do to start experimenting is opening a new Quokka file. Happy prototyping!

Use A Remote Machine As Your Dev Environment

There’s a variety of reasons why you might want to use a remote machine with an SSH server as a development environment. Because you need faster or more specialized hardware than your local machine, for example, or to debug an application running somewhere else, such as a customer site or an application in the cloud. To simplify development and troubleshooting, the Remote – SSH extension helps you do just that.

The extension runs commands and other extensions directly on the remote machine, so you won’t need any source code on your machine. Instead, you can open any folder on the remote machine and work with it just as you normally would, taking full advantage of VS Code’s full feature set. Handy!

Compile Sass In Real Time

A real-time Sass compiler with live browser reload? Ritwick Dey’s extension has got you covered. It helps you compile/transpile your SASS/SCSS files to CSS files in real time.

Features include customizing the file location of the exported CSS as well as its style and extension name, there’s a quick status bar control, you can exclude specific folders in the settings, and autoprefix is supported, too.

Tips And Tricks Nobody Bothered To Tell You

Are you really making full use of the powerful features VS Code has to offer? Burke Holland and Sarah Drasner claim you don’t, so to change that, they share all the best things about VS Code that nobody ever bothered to tell you.

From automatically updating HTML img tags with the correct size of the image to using font ligatures for better readability when coding or log points to log information out from your application, “VS Code Can Do That?!” features 36 valuable tips that’ll make your workflow even more efficient.

Wrapping Up

There are literally hundreds of VS Code extensions out there, and we hope that some of the ones listed here will prove to be useful in your day-to-day work — and most importantly help you avoid some time-consuming, routine tasks. Happy coding, everyone!

Further Reading

CSS Auditing Tools
CSS Generators
SVG Generators
HTML Email Tools and Templates
Vanilla JavaScript Code Snippets
Accessible Front-End Components
Also, subscribe to our newsletter to not miss the next ones.

Accessible SVGs: Perfect Patterns For Screen Reader Users

Original Source: https://smashingmagazine.com/2021/05/accessible-svg-patterns-comparison/

While Scalable Vector Graphics (SVGs) were first introduced in the late 90s, they have seen a massive resurgence in popularity in the last decade due to their extreme flexibility, high fidelity, and relative lightness in a world where bandwidth and performance matter more than ever. Advancements in JavaScript and the introduction of CSS media queries such @prefers-color-scheme and @prefers-reduced-motion have extended the functionality of SVGs way beyond their initial use case of simply displaying vector images on a website.

As SVG technology advances, our understanding of how we design and develop SVGs needs to advance as well. Part of that advancement includes considering the impact of such designs and code on actual humans, aka our end users.

This article outlines twelve distinct SVG patterns found “in the wild” and each alternative description announced when accessed by different combinations of operating systems, browsers, and screen readers.

Of course, the following examples are not meant to be an exhaustive list of all the possible patterns being used in the digital sphere, but they do highlight some of the more popular or ubiquitous SVG patterns you might encounter. Continue reading to discover which SVG patterns you should avoid and which patterns are the most inclusive!

Basic Alternative Descriptions Using The <img> Tag

The first group of four patterns utilizes the <img> tag linking out to an SVG file. This is a good choice for basic, uncomplicated images on your website, app, or other digital product. While the drawback to using this pattern is that you cannot easily control many visual elements or animations as an inline SVG, this pattern should render lighter and faster images overall and allow for easier maintenance on SVGs that you use in multiple locations.

Pattern #1: <img> + alt=”[words]”

<img role=”img” class=”fox” alt=”What does the fox say?” src=”https://upload.wikimedia.org/wikipedia/commons/3/39/Toicon-icon-fandom-howl.svg”>

Pattern #2: <img> + role=”img” + alt=”[words]”

<img role=”img” class=”fox” alt=”What does the fox say?” src=”https://upload.wikimedia.org/wikipedia/commons/3/39/Toicon-icon-fandom-howl.svg”>

Pattern #3: <img> + role=”img” + aria-label=”[words]”

<img role=”img” class=”fox” aria-label=”What does the fox say?” src=”https://upload.wikimedia.org/wikipedia/commons/3/39/Toicon-icon-fandom-howl.svg”>

Pattern #4: <img> + role=”img” + aria-labelledby=”[ID]”

<p id=”caption1″ class=”visually-hidden”>What does the fox say?</p>
<img role=”img” aria-labelledby=”caption1″ class=”fox” src=”https://upload.wikimedia.org/wikipedia/commons/3/39/Toicon-icon-fandom-howl.svg”>

Basic Alternative Descriptions Using The <svg> Tag

The second group of four patterns utilizes the <svg> tag with an inline SVG file. Although adding the SVG code directly into the markup could potentially make the page a bit slower to load, that minor inefficiency will be offset by having more control over the visual elements or animations of your images. By adding your SVG to the HTML directly, you also have more options when it comes to providing image information to your screen reader users.

Pattern #5: <svg> + role=”img” + <title>

<svg role=”img” …>
<title>What does the fox say?</title>
[design code]
</svg>

Pattern #6: <svg> + role=”img” + <text>

<svg role=”img” …>
<text class=”visually-hidden” font-size=”0″>What does the fox say?</text>
[design code]
</svg>

Pattern #7: <svg> + role=”img” + <title> + aria-describedby=”[ID]”

<svg role=”img” aria-describedby=”fox7″ …>
<title id=”fox7″>What does the fox say?</title>
[design code]
</svg>

Pattern #8: <svg> + role=”img” + <title> + aria-labelledby=”[ID]”

<svg role=”img” aria-labelledby=”fox8″ …>
<title id=”fox8″>What does the fox say?</title>
[design code]
</svg>

Extended Alternative Descriptions Using The <svg> Tag

The last group of four patterns utilizes the <svg> tag with an inline SVG file, much like the second group. However, in this case, we are extending the simple alternative descriptions with additional information due to the complexity of the image.

This would be a good pattern choice for more complicated images that need more explanation. However, it is important to keep in mind that there are some people with disabilities — like cognitive disorders — who might benefit from having this additional image information readily available on the screen instead of buried in the SVG code.

Depending on the type and amount of information you need to add to your SVG, you might consider taking a different approach altogether.

Pattern #9: <svg> + role=”img” + <title> + <text>

<svg role=”img” …>
<title>What does the fox say?</title>
<text class=”visually-hidden” font-size=”0″>Will we ever know?</text>
[design code]
</svg>

Pattern #10: <svg> + role=”img” + <title> + <desc>

<svg role=”img” …>
<title>What does the fox say?</title>
<desc>Will we ever know?</desc>
[design code]
</svg>

Pattern #11: <svg> + role=”img” + <title> + <desc> + aria-labelledby=”[ID]”

<svg role=”img” aria-labelledby=”fox11 description11″ …>
<title id=”fox11″>What does the fox say?</title>
<desc id=”description11″>Will we ever know?</desc>
[design code]
</svg>

Pattern #12: <svg> + role=”img” + <title> + <desc> + aria-describedby=”[ID]”

<svg role=”img” aria-describedby=”fox12 description12″ …>
<title id=”fox12″>What does the fox say?</title>
<desc id=”description12″>Will we ever know?</desc>
[design code]
</svg>

See the full CodePen Accessible SVG Pattern Comparison (Fox Version) by Carie Fisher.

SVG Pattern Winners And Losers

By running various screen readers on different combinations of operating systems and browsers, we see definite patterns emerging in the final results table. There are some clear SVG pattern winners and losers, plus a few patterns somewhere in the middle that you could implement as long as you are aware of, and can accept their limitations. Looking over the results table, we can conclude the following:

Basic Alternative Descriptions Using The <img> Tag (Group 1)
Best In Show

Pattern 2: <img> + role=”img” + alt=”[words]”
Pattern 3: <img> + role=”img” + aria-label=”[words]”

Use Caution

Pattern 4: <img> + role=”img” + aria-labelledby=”[ID]”

Not Recommended

Pattern 1: <img> + alt=”[words]”

Basic Alternative Descriptions Using The <svg> Tag (Group 2)
Best In Show

Pattern 5: <svg> + role=”img” + <title>
Pattern 8: <svg> + role=”img” + <title> + aria-labelledby=”[ID]”

Use Caution

Pattern 7: <svg> + role=”img” + <title> + aria-describedby=”[ID]”

Not Recommended

Pattern 6: <svg> + role=”img” + <text>

Extended Alternative Descriptions Using The <svg> Tag (Group 3)
Best In Show

Pattern 11: <svg> + role=”img” + <title> + <desc> + aria-labelledby=”[ID]”

Note: While this pattern is not perfect as it repeated alternative descriptions, it did not ignore any of the elements in the testing, unlike the “use caution” patterns.

Use Caution

Pattern 9: <svg> + role=”img” + <title> + <text>
Pattern 10: <svg> + role=”img” + <title> + <desc>
Pattern 12: <svg> + role=”img” + <title> + <desc> + aria-describedby=”[ID]”

Not Recommended

None of the patterns in this group completely failed the tests.

Testing Results

See the Pen Testing Results by Carie Fisher.

Wrapping Up

It is important to note that part of interpreting the results of the SVG pattern tests is understanding that creators of each screen reader have a recommended browser(s) that they fully support. This doesn’t mean you shouldn’t or couldn’t use a screen reader on a different browser, this just means that if you do, the results may not be as accurate as if you used the recommended one(s).

The pattern testing for this article did include some combinations of browsers and screen readers that may fall into the “fringe” category, but there are also notes on which combinations of operating systems, browsers, and screen readers are recommended for your own testing. The results of these tests should help you make the best SVG pattern decision possible, based on your pattern needs and constraints.

A reminder that before you settle on a pattern, please make sure you know the basics of how and when to create accessible images and that you fully understand the required alternative information needed for the different image types.

If you need additional help deciding on which pattern to use for your environment, check out the article Good, Better, Best: Untangling The Complex World Of Accessible Patterns to help you navigate the tricky waters of accessible patterns. Armed with all of this information and just a little bit of effort, your SVGs are well on their way to being more inclusive to all.

Editor’s note: You can learn best practices on accessibility with Carie in her upcoming online workshop on Accessible Front-End Patterns — with guidelines, testing tools, assistive technology and inclusive design patterns. Online, and live.

The best tablets with a stylus pen for drawing and note-taking in 2021

Original Source: http://feedproxy.google.com/~r/CreativeBloq/~3/ryW_0n0pJYs/the-best-tablets-with-a-stylus-for-drawing-and-note-taking

Swap your Moleskine for a nifty tablet with a stylus.

25 Free Magazines You Can Download From Apple App Store

Original Source: https://www.hongkiat.com/blog/free-newsstand-magazine-app-store/

Magazines are a good way to keep yourself updated and entertained about your favorite topic. And it makes an even better pastime when you can read your favorite magazine on your mobile device. In…

Visit hongkiat.com for full content.

A Guide To Undoing Mistakes With Git (Part 2)

Original Source: https://smashingmagazine.com/2021/05/undoing-mistakes-git-part2/

In this second part of our series on “Undoing Mistakes with Git”, we’ll bravely look danger in the eye again: I’ve prepared four new doomsday scenarios — including, of course, some clever ways to save our necks! But before we dive in: take a look at the check out previous articles on Git for even more self-rescue methods that help you undo your mistakes with Git!

Let’s go!

Recovering a Deleted Branch Using the Reflog

Have you ever deleted a branch and, shortly after, realized that you shouldn’t have? In the unlikely event that you don’t know this feeling, I can tell you that it’s not a good one. A mixture of sadness and anger creeps up on you, while you think of all the hard work that went into that branch’s commits, all the valuable code that you’ve now lost.

Luckily, there’s a way to bring that branch back from the dead — with the help of a Git tool named “Reflog”. We had used this tool in the first part of our series, but here’s a little refresher: the Reflog is like a journal where Git notes every movement of the HEAD pointer in your local repository. In other, less nerdy words: any time you checkout, commit, merge, rebase, cherry-pick, and so on, a journal entry is created. This makes the Reflog a perfect safety net when things go wrong!

Let’s take a look at a concrete example:

$ git branch
* feature/login
master

We can see that we currently have our branch feature/login checked out. Let’s say that this is the branch we’re going to delete (inadvertently). Before we can do that, however, we need to switch to a different branch because we cannot delete our current HEAD branch!

$ git checkout master
$ git branch -d feature/login

Our valuable feature branch is now gone — and I’ll give you a minute to (a) understand the gravity of our mistake and (b) to mourn a little. After you’ve wiped away the tears, we need to find a way to bring back this branch! Let’s open the Reflog (simply by typing git reflog) and see what it has in store for us:

Here are some comments to help you make sense of the output:

First of all, you need to know that the Reflog sorts its entries chronologically: the newest items are at the top of the list.
The topmost (and therefore newest) item is the git checkout command that we performed before deleting the branch. It’s logged here in the Reflog because it’s one of these “HEAD pointer movements” that the Reflog so dutifully records.
To undo our grave mistake, we can simply return to the state before that — which is also cleanly and clearly recorded in the Reflog!

So let’s try this, by creating a new branch (with the name of our “lost” branch) that starts at this “before” state SHA-1 hash:

$ git branch feature/login 776f8ca

And voila! You’ll be delighted to see that we’ve now restored our seemingly lost branch! ?

If you’re using a Git desktop GUI like “Tower”, you can take a nice shortcut: simply hit CMD + Z on your keyboard to undo the last command — even if you’ve just violently deleted a branch!

Luckily, these types of problems can be easily corrected. Let’s roll up our sleeves and get to work.

The first step is to switch to the correct destination branch and then move the commit overusing the cherry-pick command:

$ git checkout feature/login
$ git cherry-pick 776f8caf

You will now have the commit on the desired branch, where it should have been in the first place. Awesome!

But there’s still one thing left to do: we need to clean up the branch where it accidentally landed at first! The cherry-pick command, so to speak, created a copy of the commit — but the original is still present on our long-running branch:

This means we have to switch back to our long-running branch and use git reset to remove it:

$ git checkout main
$ git reset –hard HEAD~1

As you can see, we’re using the git reset command here to erase the faulty commit. The HEAD~1 parameter tells Git to “go back 1 revision behind HEAD”, effectively erasing the topmost (and in our case: unwanted) commit from the history of that branch.

And voila: the commit is now where it should have been in the first place and our long-running branch is clean — as if our mistake had never happened!

Editing the Message of an Old Commit

It’s all too easy to smuggle a typo into a commit message — and only discover it much later. In such a case, the good old –amend option of git commit cannot be used to fix this problem, because it only works for the very last commit. To correct any commit that is older than that, we have to resort to a Git tool called “Interactive Rebase”.

First, we have to tell Interactive Rebase which part of the commit history we want to edit. This is done by feeding it a commit hash: the parent commit of the one we want to manipulate.

$ git rebase -i 6bcf266b

An editor window will then open up. It contains a list of all commits after the one we provided as a basis for the Interactive Rebase in the command:

Here, it’s important that you don’t follow your first impulse: in this step, we do not edit the commit message, yet. Instead, we only tell Git what kind of manipulation we want to do with which commit(s). Quite conveniently, there’s a list of action keywords noted in the comments at the bottom of this window. For our case, we mark up line #1 with reword (thereby replacing the standard pick).

All that’s left to do in this step is to save and close the editor window. In return, a new editor window will open up that contains the current message of the commit we marked up. And now is finally the time to make our edits!

Here’s the whole process at a glance for you:

This is where fixup comes in. It allows you to still make this correcting band-aid commit. But here comes the magic: it then applies it to the original, broken commit (repairing it that way) and then discards the ugly band-aid commit completely!

We can go through a practical example together! Let’s say that the selected commit here is broken.

Let’s also say that I have prepared changes in a file named error.html that will solve the problem. Here’s the first step we need to make:

$ git add error.html
$ git commit –fixup 2b504bee

We’re creating a new commit, but we’re telling Git this is a special one: it’s a fix for an old commit with the specified SHA-1 hash (2b504bee in this case).

The second step, now, is to start an Interactive Rebase session — because fixup belongs to the big toolset of Interactive Rebase.

$ git rebase -i –autosquash 0023cddd

Two things are worth explaining about this command. First, why did I provide 0023cddd as the revision hash here? Because we need to start our Interactive Rebase session at the parent commit of our broken fellow.

Second, what is the –autosquash option for? It takes a lot of work off our shoulders! In the editor window that now opens, everything is already prepared for us:

Thanks to the –autosquash option, Git has already done the heavy lifting for us:

It marked our little band-aid commit with the fixup action keyword. That way, Git will combine it with the commit directly above and then discard it.
It also reordered the lines accordingly, moving our band-aid commit directly below the commit we want to fix (again: fixup works by combining the marked-up commit with the one above!).

In short: There’s nothing to do for us but close the window!

Let’s take a final look at the end result.

The formerly broken commit is fixed: it now contains the changes we prepared in our band-aid commit.
The ugly band-aid commit itself has been discarded: the commit history is clean and easy to read — as if no mistake had occurred at all.

Knowing How to Undo Mistakes is a Superpower

Congratulations! You are now able to save your neck in many difficult situations! We cannot really avoid these situations: no matter how experienced we are as developers, mistakes are simply part of the job. But now that you know how to deal with them, you can face them with a laid-back heart rate. ?

If you want to learn more about undoing mistakes with Git, I can recommend the free “First Aid Kit for Git”, a series of short videos about exactly this topic.

Have fun making mistakes — and, of course, undoing them with ease!

How to Create an Appointment Scheduling Platform: DIY vs. Plugin vs. Trafft

Original Source: http://feedproxy.google.com/~r/tympanus/~3/qZ3qLwIHePg/

Websites equipped with appointment scheduling tools and booking calendars make everyone’s lives easier. 

For starters, business owners can entrust the often time-consuming and tedious work of scheduling appointments to the website. Customers and prospects, on the other hand, have a faster and more convenient way of booking and managing appointments on their own.

That said, when it comes time to add a booking system to a client’s website, what should you build it with? Is there one option that’s better than another? 

In this post, we’ll weigh the the following options: 

Code it from-scratchUse a pluginUse a business automation platform like Trafft

Comparing booking solution options for websites

You have three options when it comes to implementing a booking solution on a client’s site: 

Do-it-yourself and code the booking platform from scratch.Install a WordPress plugin and do light customizations and configurations.Use Trafft’s feature-rich premium appointment scheduling software.

Let’s see how they compare: 

Cost

DIY

This option might seem like it’s free since you’re the one doing all the work. However, the cost of your time can be tremendous depending on how robust a solution you plan to build. 

For instance, if you’re building a complete scheduling system, you’ll need to account for different services, locations, employees, and time slots. You’ll also have to build out features like payment processing, taxation, invoice generation, SMS reminders, and more. 

Plugin 

There are technically free WordPress plugins that’ll handle this. However, there’s going to be a trade-off in terms of features and control. Even freemium plugins have their limits. 

Trafft 

Trafft is a freemium software. You can use the free plan to do a test run of the platform or to implement basic booking systems for small business clients.    

The premium plans give you access to all the features you need to build the booking system you envisioned for your client… Without having to do the work of building it yourself. You even get to choose which custom features — like group booking, special days, and Zoom integration — are included. 

Setup

DIY 

Out of the three options, this one is going to take the longest, for obvious reasons. Even once you get the hang of building an appointment scheduling system, expect to spend hundreds to thousands of hours on each project. 

Plugin

While plugins might provide seemingly ready-made booking systems, they’re dependent on having a website up and running. 

So, before you even think about working on this, you’ll need to buy a web hosting plan and domain, design the site, and make sure all the themes, plugins, and other modules look good and work well together. What’s more, there’s a ton of maintenance afterwards — managing backups, data security, server uptime, app integration, and much, much more. 

Trafft 

Using Trafft to set up an appointment scheduling system is so easy. You’ll get started the second you enter the platform: 

You can configure the business’s availability, who can manage the booking system, as well as what services can be scheduled. 

Once your account is set up, general settings allow you to refine the rules of appointment scheduling. That way, your client has less to do in terms of reviewing, adjusting, and overall managing their bookings. 

One other thing worth mentioning is that Trafft isn’t dependent on a content management system like WordPress. You have the flexibility of designing a free-standing scheduling platform or you can integrate it with a website. So, you could realistically have this up and running in less than an hour. 

Customization

DIY 

The world is your oyster. However, advanced customization can take quite a while to complete, which means potentially leaving your client without an appointment scheduling platform for weeks or even months.

Plugin 

Free plugins mean limited features, control, and support. As a result of this, an insufficient or lackluster appointment scheduling system could become a liability for your client. 

Freemium plugins offer more control and features, but you still have to conform to what the developer envisioned for the booking system in terms of design and features. 

Trafft 

You’ll have the ability to do light tweaks to the design of your Trafft booking system. Things like colors, fonts, and booking steps can easily be updated for your booking website. 

If you’d prefer to do more customizing, you can use the custom CSS and JavaScript fields to give your platform a more branded style. 

As far as features go, Trafft has accounted for the features and integrations you’d need for the majority of booking systems. After you sign up for Trafft, you’ll soon discover that advanced features have already been set up for you: 

You can easily deactivate the features or integrations you don’t need and then add in the ones that you do. But this’ll at least keep you from having to enable every feature from-scratch.

It’s just as easy to set up integrations like Zoom meetings or PayPal or Stripe payment processing. Enable the integrations, add your account details, and save. 

Automation

DIY 

In addition to building out your booking platform, you’ll have to manually connect the other apps needed to streamline the scheduling process. So, make sure to add the additional time retrieving the API information and hooking them up to your system when considering this option.

Plugin

There are limits to which software you can connect to a WordPress plugin. 

If the app you want to use doesn’t have a built-in integration, you always use IFTTT or Zapier. However, your booking system needs to be compatible with common business software in order for that to happen, so your client may end up having to manually manage these business processes anyway. 

Trafft

Trafft was built to be a business automation platform. So if your client needs a truly streamlined scheduling process, this is going to be your best bet. 

For instance, the appointment booking process takes customers through a series of questions. Like who they want to schedule the appointment or service with:

All of those details get hashed out online. What’s more, once the appointment is booked, notifications go out to both the customer and employee so everyone is kept in the loop about appointments and changes to them. 

Also, with software integrations already built into the Trafft platform, things like collecting payments, managing VAT or other taxes, creating Zoom meeting room, generating invoices, and so on happen automatically. 

Niche Compatibility

DIY

Different business types need different booking solutions. So, the more variety there is in your client base, the more booking systems you’ll have to learn how to build. This’ll only slow things down even further for you. 

Plugin

This is another limitation you’re likely to encounter when using an open source plugin. While some might offer different types of booking calendars, lists, or displays, they’re not always the best option for every niche.

Trafft 

Trafft was built with different industries/niches in mind. For example: 

SalonsHealthcareSports and trainingEducation and consultingAdministrationEvents and entertainmentProfessional servicesPersonal services

When you first sign up for Trafft, one of the first things you’ll be asked is what type of business it’s for. This helps the platform configure the settings for the chosen niche. 

Support

DIY 

If you build it, you’re responsible for supporting, maintaining, and debugging it. 

Plugin

If you’re using a free plugin, you might not get much in the way of support when something goes wrong with it. Even with freemium plugins, there’s no guarantee that the developer will be immediately available to help you troubleshoot an issue. 

The longer you have to wait to get something resolved, the more it’ll cost your clients business.

Trafft

With premium scheduling software like Trafft, support is part of what you pay for. 

To start, Trafft has a self-help Knowledgebase for its users:

Within the app itself, users will also find a live chat form for more immediate questions or issues. 

You can always email them for help as well. 

Bottom line: Premium support is built into a Trafft subscription, so you and your clients will never be stuck with a faulty booking system.

How will you add appointment scheduling to your clients’ sites?

There are clear benefits to each of the options presented here. 

With DIY, you have total control in terms of design, features, and integrations. However, it’s going to be quite costly in terms of the amount of time you have to put into building and supporting it. 

With a WordPress plugin, you have the speed and ease of implementation. However, you’ll have to build your appointment scheduling platform around the limitations of the plugin and the developer’s vision for it.

With the Trafft premium appointment scheduling software, you get the best of both worlds. It’s insanely easy to get a booking platform up and running and you have more control over how it works and looks. What’s more, you can have it in your clients’ hands in no time at all.Having a robust scheduling tool can be a real game-changer for business. If you want to build something that looks professional, is universally user-friendly, and empowers your clients to schedule appointments and start making more money starting tomorrow, Trafft is the logical choice.

The post How to Create an Appointment Scheduling Platform: DIY vs. Plugin vs. Trafft appeared first on Codrops.

20 Best New Websites, May 2021

Original Source: https://www.webdesignerdepot.com/2021/05/20-best-new-websites-may-2021/

This month we have several examples of brutalism used to good effect as a foil to showcase products and/or work. By contrast, at the other end of the scale, we have brands who have chosen to go more of an immersive experience route, using full-screen images, sound, animation, and even VR.

Both are valid approaches, depending on the content. The former tends to work better as a backdrop for artwork, photography, and artisanal craft goods — acting as a virtual gallery space — while the latter is better for consumer goods and experiences, particularly food, drink, and accommodation.

There is, of course, a whole range in between these extremes, and we’ve got that covered too. Enjoy!

Grainne Morton

A simple layout, soft pastel colors, and clear navigation provide an excellent backdrop for Grainne Morton’s handmade jewelry creations.

Gage Hotel

Good photography and a heritage-inspired color scheme give the Gage Hotel’s site a luxury feel.

Tejidos Roca

Tejidos Roca is a fabric manufacturer, and the design of their site uses a circle motif to bring rolls of fabric to mind.

La Passation Synerghetic 2021

Synerghetic is part of the Junior Enterprises Europe scheme – a network of businesses run by students. This year they are not holding the usual handover ceremony, so Synerghetic created this rather fun little digital celebration instead.

Redwood Empire

For Earth Month, Redwood Empire Whiskey has created a microsite promoting a competition styled to match their bottle labels.

Gabriel Cuallado

This site focusing on Spanish photographer Gabriel Cullado’s life and work features some great transitions and good use of horizontal scrolling.

Ombia Studio

In Ombia Studio’s site, atmospheric photographs stand out in a minimal layout. There is a sense of almost gallery curation here.

Headup

Headup uses a pleasing color scheme and geometric graphics to create a welcoming but businesslike approach.

the Figo

Spherical curves and line animations create interest in this site for boutique hotel, the Figo.

Boon Market

Boon Market is about promoting a toxin-free and waste-free lifestyle, and their site reflects this with its use of simple type and soft colors.

Unspoken Agreement

Unspoken Agreement’s website has a quietly confident feel, with clean lines and some pleasing type.

hnst

Another brutalist-inspired design here, but the use of bright red makes it fresh in hnst’s take on the style.

InteriorLAB

Part minimalist, part glossy magazine, InteriorLAB have succeeded in making the simple feel luxurious.

Bowmore Experience

Bowmore has opted for immersive video and visually beautiful images to present their limited-edition Timeless whisky range.

Oly Sheet

There is a slightly old-school start-up feel to Oly Sheet’s website, but it is still appealing with fresh, spring colors and well-organized content.

Aalto University

Aalto University has provided a pretty in-depth tour of its campus here. The navigation is clear, and the information is presented in ideal-sized chunks — enough detail, but not too much at once.

Wisr

Wisr features a Heath Robinson style machine that ‘runs’ as the user scrolls down the page. It provides a bit of interest (no pun intended) to the not very exciting subject of personal loans.

Rudl und Schwarm

Bright colors, cute, but not too cutesy, illustration, some nice scrolling, and transition effects are used really well on Rudl und Schwarm. And it’s got bees; bees are good.

Dr. Maul

This site for Dr. T. Maul manages to take orthodontistry past the usual image of uncomfortable wiring, elastic bands, and ‘train tracks and make it seem just a little more glamorous.

My Drink

There is a slightly vintage feel to this site for My Drink with its cocktail illustration. The blue text on grey is soothing without being bland.

Bonus Site: Imperial Style Guide

And finally not new, but a bonus in honor of May 4th, the Imperial style guide. Well, the Web would get boring if it was serious all the time.

Source

p img {display:inline-block; margin-right:10px;}
.alignleft {float:left;}
p.showcase {clear:both;}
body#browserfriendly p, body#podcast p, div#emailbody p{margin:0;}

The post 20 Best New Websites, May 2021 first appeared on Webdesigner Depot.

CSS Container Queries: Use-Cases And Migration Strategies

Original Source: https://smashingmagazine.com/2021/05/css-container-queries-use-cases-migration-strategies/

When we write media queries for a UI element, we always describe how that element is styled depending on the screen dimensions. This approach works well when the responsiveness of the target element media query should only depend on viewport size. Let’s take a look at the following responsive page layout example.

However, responsive Web Design (RWD) is not limited to a page layout — the individual UI components usually have media queries that can change their style depending on the viewport dimensions.

You might have already noticed a problem with the previous statement — individual UI component layout often does not depend exclusively on the viewport dimensions. Whereas page layout is an element closely tied to viewport dimensions and is one of the topmost elements in HTML, UI components can be used in different contexts and containers. If you think about it, the viewport is just a container, and UI components can be nested within other containers with styles that affect the component’s dimensions and layout.

Even though the same product card component is used in both the top and bottom sections, component styles not only depend on the viewport dimensions but also depend on the context and the container CSS properties (like the grid in the example) where it’s placed.

Of course, we can structure our CSS so we support the style variations for different contexts and containers to address the layout issue manually. In the worst-case scenario, this variation would be added with style override which would lead to code duplication and specificity issues.

.product-card {
/* Default card style */
}

.product-card–narrow {
/* Style variation for narrow viewport and containers */
}

@media screen and (min-width: 569px) {
.product-card–wide {
/* Style variation for wider viewport and containers */
}
}

However, this is more of a workaround for the limitations of media queries rather than a proper solution. When writing media queries for UI elements we are trying to find a “magic” viewport value for a breakpoint when the target element has minimum dimensions where the layout doesn’t break. In short, we are linking a “magical” viewport dimension value to element dimensions value. This value is usually different from than viewport dimension and is prone to bugs when inner container dimensions or layout changes.

The following example showcases this exact issue — even though a responsive product card element has been implemented and it looks good in a standard use-case, it looks broken if it’s moved to a different container with CSS properties that affect element dimensions. Each additional use-case requires additional CSS code to be added which can lead to duplicated code, code bloat, and code that is difficult to maintain.

In case you are using a browser that doesn’t support container queries, an image showcasing the intended working example will be provided alongside the CodePen demo.

Working With Container Queries

Container queries are not as straightforward as regular media queries. We’ll have to add an extra line of CSS code to our UI element to make container queries work, but there’s a reason for that and we’ll cover that next.

Containment Property

CSS contain property has been added to the majority of modern browsers and has a decent 75% browser support at the time of writing this article. The contain property is mainly used for performance optimization by hinting to the browser which parts (subtrees) of the page can be treated as independent and won’t affect the changes to other elements in a tree. That way, if a change occurs in a single element, the browser will re-render only that part (subtree) instead of the whole page. With contain property values, we can specify which types of containment we want to use — layout, size, or paint.

There are many great articles about the contain property that outline available options and use-cases in much more detail, so I’m going to focus only on properties related to container queries.

What does the CSS contentment property that’s used for optimization have to do with container queries? For container queries to work, the browser needs to know if a change occurs in the element’s children layout that it should re-render only that component. The browser will know to apply the code in the container query to the matching component when the component is rendered or the component’s dimension changes.

We’ll use the layout​ and style​ values for the contain​ property, but we’ll also need an additional value that signals the browser about the axis in which the change will occur.

inline-size
Containment on the inline axis. It’s expected for this value to have significantly more use-cases, so it’s being implemented first.
block-size
Containment on block axis. It’s still in development and is not currently available.

One minor downside of the contain property is that our layout element needs to be a child of a contain element, meaning that we are adding an additional nesting level.

<section>
<article class=”card”>
<div class=”card__wrapper”>
<!– Card content –>
</div>
</article>
</section>

.card {
contain: layout inline-size style;
}

.card__wrapper {
display: grid;
grid-gap: 1.5em;
grid-template-rows: auto auto;
/* … */
}

Notice how we are not adding this value to a more distant parent-like section and keeping the container as close to the affected element as possible.

“Performance is the art of avoiding work and making any work you do as efficient as possible. In many cases, it’s about working with the browser, not against it.”

— “Rendering Performance,” Paul Lewis

That is why we should correctly signal the browser about the change. Wrapping a distant parent element with a contain property can be counter-productive and negatively affect page performance. In worst-case scenarios of misusing the contain property, the layout may even break and the browser won’t render it correctly.

Container Query

After the contain property has been added to the card element wrapper, we can write a container query. We’ve added a contain property to an element with card class, so now we can include any of its child elements in a container query.

Just like with regular media queries, we need to define a query using min-width or max-width properties and nest all selectors inside the block. However, we’ll be using the @container keyword instead of @media to define a container query.

@container (min-width: 568px) {
.card__wrapper {
align-items: center;
grid-gap: 1.5em;
grid-template-rows: auto;
grid-template-columns: 150px auto;
}

.card__image {
min-width: auto;
height: auto;
}
}

Both card__wrapper and card__image element are children of card element which has the contain property defined. When we replace the regular media queries with container queries, remove the additional CSS classes for narrow containers, and run the CodePen example in a browser that supports container queries, we get the following result.

In this example, we’re not resizing the viewport, but the <section> container element itself that has resize CSS property applied. The component automatically switches between layouts depending on the container dimensions. (Large preview)

See the Pen Product Cards: Container Queries by Adrian Bece.

Please note that container queries currently don’t show up in Chrome developer tools, which makes debugging container queries a bit difficult. It’s expected that the proper debugging support will be added to the browser in the future.

You can see how container queries allow us to create more robust and reusable UI components that can adapt to virtually any container and layout. However, proper browser support for container queries is still far away in the feature. Let’s try and see if we can implement container queries using progressive enhancement.

Progressive Enhancement & Polyfills

Let’s see if we can add a fallback to CSS class variation and media queries. We can use CSS feature queries with the @supports rule to detect available browser features. However, we cannot check for other queries, so we need to add a check for a contain: layout inline-size style value. We’ll have to assume that browsers that do support inline-size property also support container queries.

/* Check if the inline-size value is supported */
@supports (contain: inline-size) {
.card {
contain: layout inline-size style;
}
}

/* If the inline-size value is not supported, use media query fallback */
@supports not (contain: inline-size) {
@media (min-width: 568px) {
/* … */
}
}

/* Browser ignores @container if it’s not supported */
@container (min-width: 568px) {
/* Container query styles */
}

However, this approach might lead to duplicated styles as the same styles are being applied both by container query and the media query. If you decide to implement container queries with progressive enhancement, you’d want to use a CSS pre-processor like SASS or a post-processor like PostCSS to avoid duplicating blocks of code and use CSS mixins or another approach instead.

See the Pen Product Cards: Container Queries with progressive enhancement by Adrian Bece.

Since this container query spec is still in an experimental phase, it’s important to keep in mind that the spec or implementation is prone to change in future releases.

Alternatively, you can use polyfills to provide a reliable fallback. There are two JavaScript polyfills I’d like to highlight, which currently seem to be actively maintained and provide necessary container query features:

cqfill by Jonathan Neal
JavaScript polyfill for CSS and PostCSS
react-container-query by Chris Garcia
Custom hook and component for React

Migrating From Media Queries To Container Queries

If you decide to implement container queries on an existing project that uses media queries, you’ll need to refactor HTML and CSS code. I’ve found this to be the fastest and most straightforward way of adding container queries while providing a reliable fallback to media queries. Let’s take a look at the previous card example.

<section>
<div class=”card__wrapper card__wrapper–wide”>
<!– Wide card content –>
</div>
</section>

/* … */

<aside>
<div class=”card__wrapper”>
<!– Narrow card content –>
</div>
</aside>

.card__wrapper {
display: grid;
grid-gap: 1.5em;
grid-template-rows: auto auto;
/* … */
}

.card__image {
/* … */
}

@media screen and (min-width: 568px) {
.card__wrapper–wide {
align-items: center;
grid-gap: 1.5em;
grid-template-rows: auto;
grid-template-columns: 150px auto;
}

.card__image {
/* … */
}
}

First, wrap the root HTML element that has a media query applied to it with an element that has the contain property.

<section>
<article class=”card”>
<div class=”card__wrapper”>
<!– Card content –>
</div>
</article>
</section>

@supports (contain: inline-size) {
.card {
contain: layout inline-size style;
}
}

Next, wrap a media query in a feature query and add a container query.

@supports not (contain: inline-size) {
@media (min-width: 568px) {
.card__wrapper–wide {
/* … */
}

.card__image {
/* … */
}
}
}

@container (min-width: 568px) {
.card__wrapper {
/* Same code as .card__wrapper–wide in media query */
}

.card__image {
/* Same code as .card__image in media query */
}
}

Although this method results in some code bloat and duplicated code, by using SASS or PostCSS you can avoid duplicating development code, so the CSS source code remains maintainable.

Once container queries receive proper browser support, you might want to consider removing @supports not (contain: inline-size) code blocks and continue supporting container queries exclusively.

Stephanie Eckles has recently published a great article on container queries covering various migration strategies. I recommend checking it out for more information on the topic.

Use-Case Scenarios

As we’ve seen from the previous examples, container queries are best used for highly reusable components with a layout that depends on the available container space and that can be used in various contexts and added to different containers on the page.

Other examples include (examples require a browser that supports container queries):

Modular components like cards, form elements, banners, etc.
Adaptable layouts
Pagination with different functionalities for mobile and desktop
Fun experiments with CSS resize

Conclusion

Once the spec has been implemented and widely supported in browsers, container queries might become a game-changing feature. It will allow developers to write queries on component level, moving the queries closer to the related components, instead of using the distant and barely-related viewport media queries. This will result in more robust, reusable, and maintainable components that will be able to adapt to various use-cases, layouts, and containers.

As it stands, container queries are still in an early, experimental phase and the implementation is prone to change. If you want to start using container queries in your projects today, you’ll need to add them using progressive enhancement with feature detection or use a JavaScript polyfill. Both cases will result in some overhead in the code, so if you decide to use container queries in this early phase, make sure to plan for refactoring the code once the feature becomes widely supported.

References

“Container Queries: A Quick Start Guide” by David A. Herron
“Say Hello To CSS Container Queries,” Ahmad Shadeed
“CSS Containment In Chrome 52,” Paul Lewis
“Helping Browsers Optimize With The CSS Contain Property,” Rachel Andrew