Semantic UI in Markdown posts

Configuring Gatsby to render Markdown using Semantic UI components.

2019-02-15GatsbySemantic UI


Introduction

In Gatsby: Setting up a simple site we set up Gatsby to process markdown files to blog pages, including highlighting code samples.

Out of the box, using the Semantic UI styles will handle simple elements in markdown, for example headers. However we can get better styling by using Semantic UI components from our markdown.

The initial setup is covered by the docs on remark custom components. We will tweak this to render some Semantic UI components:

Setup

  1. Install rehype-react:

    yarn add rehype-react
  2. In the page or component where you query and display posts, make a function to render the htmlAST version of your markdown using Semantic components:

    const renderAst = new rehypeReact({
      createElement: React.createElement,
      components: { 
        'ul': SemanticUL, 
        'ol': SemanticOL,
        'li': SemanticLI 
      },
    }).Compiler

    This will render list elements as custom components, which we will create next.

  3. Create new components for lists:

    const SemanticOL = ({children}) => <List relaxed ordered as='ol'>{children}</List> 
    const SemanticUL = ({children}) => <List relaxed bulleted as='ul'>{children}</List> 
    const SemanticLI = ({children}) => <List.Item as='li'>{children}</List.Item> 

    Both of these just pass through children to a List with the appropriate props set. If you prefer a different style of Semantic list, just tweak the props.

  4. Update the graphQL query to retrieve htmlAst instead of html.
  5. Render the post with:

    renderAst(post.htmlAst)

    rather than:

    <div dangerouslySetInnerHTML={{ __html: post.html }} />

    This will get you nicely styled lists - but other Semantic components can be added in the same way. See the caveats for some gotchas!

  6. The default styling for ul and ol lists in semantic doesn’t respect the relaxed modifier. We can use list.overrides to make line spacing match paragraphs, and to use the same relaxed padding as other Semantic lists:

    ul.ui.list.relaxed li,
    ol.ui.list.relaxed li,
    .ui.list.relaxed > .item,
    .ui.list.relaxed .list > .item {
      line-height: @paragraphLineHeight;
    }
    
    ul.ui.list.relaxed li:not(:first-child),
    ol.ui.list.relaxed li:not(:first-child),
    .ui.list.relaxed > .item:not(:first-child),
    .ui.list.relaxed .list > .item:not(:first-child) {
      padding-top: @relaxedItemVerticalPadding;
    }
    
    ul.ui.list.relaxed li:not(:last-child),
    ol.ui.list.relaxed li:not(:last-child),
    .ui.list.relaxed > .item:not(:last-child),
    .ui.list.relaxed .list > .item:not(:last-child) {
      padding-bottom: @relaxedItemVerticalPadding;
    }
  7. For extra credit, we can add another component mapping:

    'icon': Icon

    and use it in markdown:

    *Made with <icon name='heart' color='violet'></icon> by rebeam.*

    Which should produce the following rather cliché result:

    Made with by rebeam.

  8. We can also display tables with Semantic:

    | Bridge       | Designer       | Length |
    |--------------|----------------|-------:|
    | Brooklyn     | J. A. Roebling |   1595 |
    | Manhattan    | G. Lindenthal  |   1470 |
    | Williamsburg | L. L. Buck     |   1600 |
    BridgeDesignerLength
    BrooklynJ. A. Roebling1595
    ManhattanG. Lindenthal1470
    WilliamsburgL. L. Buck1600

    This needs the following custom components:

    'table': Table,
    'thead': Table.Header,
    'tr': Table.Row,
    'td': Table.Cell

    Table can also be customised - the example above uses celled color='violet', see templates/blog-post.js.

  9. We can display blockquotes with a Semantic Message with a quote icon:

    const MessageInfo = ({children}) => 
     <Message icon>
       <Icon name='quote left' color='black'></Icon>
       <Message.Content>
         {children}
       </Message.Content>
     </Message> 

    Register this with 'blockquote': MessageInfo in renderAst. This will give you:

    > **Nicely formatted blockquotes**  
    > Quote all the things.

    Note we need a couple of spaces on the end of the first line to get a linebreak without a new paragraph.

    You can still use other types of component directly, for example here is Alert from react-bootstrap. Just register the component directly, e.g. 'alert': Alert:

    <alert variant="danger">**Scary message!**</alert>