DEV Community

Stacey
Stacey

Posted on • Updated on

Hiding Images with Content Warnings in React

After attending Tori Clark and Keli Sierra Bradley’s axe-con talk on trigger and content warnings, I put making a toggle-able content warning for my blog on my to-do list.

By content warning, I’m envisioning something similar to Instagram’s current implementation. In the screenshot included below, the account belonging to horror magazine Fangoria has age-gated some of its especially gnarly images. The image is blurred beyond recognition with text overlaying it that lets users know why it is hidden. Lastly, there is a button to click to reveal the image.

screen grab of a censored photo on Fangoria's Instagram account

In my past life in academia, I wrote a lot about horror video games, and would like to better integrate that interest into my portfolio. Given that most of my website is about my current experience as a front-end developer, including a warning before the, uh, grosser of that content sounded like a good idea.

In their talk, Clark and Bradley compared content warnings to wet floor signs: the warning benefits everyone, but some people are more likely to slip than others. I interpreted that to mean, if you have an image you think might be triggering to someone, it’s best to err on the side of caution and give users the opportunity to opt into seeing it. The worst case is that a user may have to take an extra action to see it, which, I think, is worth the payoff of potentially ruining someone’s day by not hiding it. I specifically brought up content warnings in terms of hiding horror, but it could have a lot of other, comparatively benign applications such as hiding a spoiler from a popular TV show or respecting culture-specific sensitivities.

My portfolio currently uses GatsbyJS, so React is going to be the way to go for me, and JSX makes it very easy to hide and show HTML elements inline. That said, this should be relatively easy to adapt to other frameworks like Vue or even vanilla Javascript if we break it down effectively.

The elements of our component

Just taking the instagram post as an example, we know we need at least four basic pieces for our component:

  1. an image
  2. alt text
  3. toggle state management
  4. warning copy

If we are making this a re-usable React component, we know that at a bare minimum, the image will be different each time. Therefore, we’ll pass the image url and alt text in as props. We may want to start with something like:

const ContentWarning = ({imgSrc, alt} => {
    return (<div>
        <img src={imgSrc} alt={alt} />
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

Continuing down our list, we know we need a way to manage state, which we can easily use in modern React with the useState hook. We’ll also go ahead and throw in our warning text.

const ContentWarning = ({imgSrc, alt} => {
    const [showContent, setShowContent] = React.useState(false);
    return (<div>
        <img src={imgSrc} alt={alt} />
        <div className="warning-text">
              This image may contain sensitive content
     </div>
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

So what all do we need to control with our state? We know we want to disguise the image and show our warning text when showContent is false, but we also should be careful to disguise our alt text so that a screenreader user also isn’t inadvertently exposed to something they don’t want to hear. All images must have an alt attribute in compliance with WCAG, but that attribute can be empty—no space, just empty—so we’ll add a ternary function to check for whether showContent is true. Similarly we’ll use the logical and operator to only display the content warning if showContent is false.

Fortunately, blurring the image only requires a single line of code in modern CSS! We will similarly only include that if showContent is false.

const ContentWarning = ({imgSrc, alt} => {
    const [showContent, setShowContent] = React.useState(false);
    return (<div>
        <img 
                src={imgSrc}
                style={{ filter: !showContent ? "blur(1.5rem)" : "none" }}
                alt={showContent ? alt : ""} />
        {!showContent && <div className="warning-text">
              This image may contain sensitive content
     </div>}
    </div>)
}
Enter fullscreen mode Exit fullscreen mode

Finally, to manage the state of our content, we need a button for users to click to toggle the image:


const ContentWarning = ({imgSrc, alt} => {
    const [showContent, setShowContent] = React.useState(false);
    return (<div>
        <img 
                src={imgSrc}
                style={{ filter: !showContent ? "blur(1.5rem)" : "none" }}
                alt={showContent ? alt : ""} />
        {!showContent && <div className="warning-text">
              This image may contain sensitive content
     </div>}
<button
          className="toggle-button"
          type="button"
          onClick={() => setShowContent(!showContent)}
        >
          {showContent ? "Hide" : "Show"} Image
        </button>
    </div>)
}

Enter fullscreen mode Exit fullscreen mode

(remember to add a type to your button, folks!)

Putting it all together

To use our component, the code would look like this:

<ContentWarning
      imgSrc="https://picsum.photos/id/1025/500/300"
      alt="an adorable pug sitting upright while wrapped in a blanket in the woods"
    />
Enter fullscreen mode Exit fullscreen mode

So we put that all together and we see our image hiding and showing based on our state… all good! ….Right?

Actually, there is at least one more step. Remember how I mentioned we also wanted to render the alt text conditionally based on whether the content was hidden or not? In its current implementation, this does add the alt text back to the page when we show the image, but a screen reader user won't immediately get that new information like a sighted user will when they see the instant visual change. There are a couple ways to solve for this, and I think the simplest solution may be to just wrap our image in a live region.

const ContentWarning = ({ imgSrc, alt }) => {
    const [showContent, setShowContent] = React.useState(false);

    return (
      <div>
        <div aria-live="polite">
          <img
            style={{ filter: !showContent ? "blur(1.5rem)" : "none" }}
            src={imgSrc}
            alt={showContent ? alt : ""}
          />
          {!showContent && (
            <div className="warning-text">
              This image may contain sensitive content
            </div>
          )}
        </div>
        <button
          className="toggle-button"
          type="button"
          onClick={() => setShowContent(!showContent)}
        >
          {showContent ? "Hide" : "Show"} Image
        </button>
      </div>
    );
  }; 
Enter fullscreen mode Exit fullscreen mode

This way, it will announce the new text after a user hits the toggle button.

Here’s a Codepen example with some small style tweaks to make it a little more presentable (don’t worry, the image is an inoffensive and very cute dog).

Potential enhancements

Global Toggles

On social media platforms like Twitter, users have the option of opting in and out of seeing “media that may contain sensitive content.” We therefore may also want to have a site-wide toggle that will hide or reveal all images on the page.

To do this: instead of just passing the image url and alt text as props, we would also pass something like globalShowContent

const ContentWarning = ({ imgSrc, alt, globalShowContent}) => {}
Enter fullscreen mode Exit fullscreen mode

If we still want users to be able to toggle individual images, we would start by setting our component level state as const [showContent, setShowContent] = React.useState(globalShowContent); to respect the global setting from the jump.

Srcset instead of Src

Since we are well into 2022 at this point, you may want to pass in multiple image sources a la srcset, to do this, in addition to passing in a string with imgSrc, we could pass an array of strings containing our urls and sizes (["https://picsum.photos/id/1025/500/300 x1”, “https://picsum.photos/id/1025/1000/600 x2”]), and map it in our component.

<img src={imgSrc}
     alt={showContent ? alt : ""}
         srcSet={imgSrcSet.map(imgSrc=>imgSrc)} />
Enter fullscreen mode Exit fullscreen mode

Conclusion

As Clark and Bradley called out in their talk, ensuring users can safely access your content is an accessibility issue, and content warnings are a relatively straightforward way to do that. Whether it’s gating potentially-triggering content or just hiding a spoiler, it’s worth putting basic safe guards in place to ensure your users have a smooth, pain-free experience. Hopefully this example proves to be an easy baseline for starting your component.

Edit to correct typos; cross-posted from my site

Top comments (0)