Skip to content

ReadMore

Blog sites typically use a ReadMore control to display a short introductory prompt followed by a Read More… link which loads the remaining post content on demand.

Example

Sample ReadMore Control
  • Hypermedia

    HTML as a response to API requests
    Why are most API calls returing JSON when the browser is optimized to process HTML?
  • HTMX

    Adding functionality to any HTML element
    Can any element issue requests and update the DOM?
  • Tailwind CSS

    Respnsive, mobile first styling
    A defined set of utility classes makes it easier to prototype user interfaces.
  • HyperScript

    New client side scripting language
    Add client side functionality with an easy to learn English-prose syntax patterned after Hypertalk
  • Astro

    Fast, robust Vite-based build tool featuring static and server rendered pages
    Utilize an island architecture to selectively hydrate content on the client side.

Description

A ReadMore control presents a short introductory text block followed by a clickable “Read More” link which reveals the remaining content in an expandable block below the introduction.

ReadMore controls allow the developer to place multiple blog posts or other titles onto a page while conserving vertical real estate. The users is still able to access the full text of each post or article by simple clicking.

Blog sites are the most common use case for ReadMore controls. Multiple blog posts can be displayed on one page using ReadMore items. Each post is introduced with an opening title sentence, often containing the author and posting date. The remaining content is hidden from view until the user clicks the “Read More” link, at which point the content is revealed.

Another variation is the present the blog comments in a ReadMore control below the blog post itself.

The action of a ReadMore control is very similar to an Accordion control. The main difference is the button which reveals the hidden content is placed after the introductory text. Also, an alternative caption is displayed when the content is revealed, for example ‘Read Less…‘. A twirl icon rotates when opened which offers further confirmation that hidden content is now being displayed.

For this example, two Astro components are used (ReadMoreGroup_A and ReadMoreItem_A). The ReadMoreGroup provides a wrapper <ul> and a default slot for passing in multiple ReadMoreItems.

The basic source code to implement the examples is shown below. The code sections below contains the props, scripts, and styling of both Astro components. In order to use this ReadMore example, you will need to set up analagous custom API endpoints and return appropriate html responses but the example code should help you get started.

Read More Control Example
---
import ReadMoreItem_A from '/components/ReadMoreItem_A.astro'
import ReadMoreGroup_A from '/components/ReadMoreGroup_A.astro'
const tw = {
intro: 'ml-2 mt-2 text-lg'
}
---
<ReadMoreGroup_A closeSiblings={true} styles="mx-6">
<ReadMoreItem_A id="0" path="/api/readmore" >
<h3>Hypermedia</h3>
<div class={tw.intro}>
<h5>HTML as a response to API requests</h5>
<h5>Why are most API calls returing JSON when the browser is optimized to process HTML?</h5>
</div>
</ReadMoreItem_A>
<ReadMoreItem_A id="1" path="/api/readmore" >
<h3>HTMX</h3>
<div class={tw.intro}>
<h5>Adding functionality to any HTML element</h5>
<h5>Can any element issue requests and update the DOM?</h5>
</div>
</ReadMoreItem_A>
<ReadMoreItem_A id="2" path="/api/readmore">
<h3>Tailwind CSS</h3>
<div class={tw.intro}>
<h5>Respnsive, mobile first styling</h5>
<h5>A defined set of utility classes makes it easier to prototype user interfaces.</h5>
</div>
</ReadMoreItem_A>
<ReadMoreItem_A id="3" path="/api/readmore">
<h3>HyperScript</h3>
<div class={tw.intro}>
<h5>New client side scripting language</h5>
<h5>Add client side functionality with an easy to learn English-prose syntax patterned after Hypertalk</h5>
</div>
</ReadMoreItem_A>
<ReadMoreItem_A id="4" path="/api/readmore">
<h3>Astro</h3>
<div class={tw.intro}>
<h5>Fast, robust Vite-based build tool featuring static and server rendered pages</h5>
<h5>Utilize an island architecture to selectively hydrate content on the client side.</h5>
</div>
</ReadMoreItem_A>
</ReadMoreGroup_A>

Features

This ReadMore Control features include the following:

  1. The trigger button caption and alternative caption can be sent as props

  2. The maximum height of the exposed content can be adjusted via props

  3. The transition duration is customizable

  4. Styling for the readMore flex container, trigger button, and twirl icon can be customized using Tailwind utility classes passed via props

  5. A custom twirl icon can be used

Components

ReadMoreGroup_A

This component provides a general <ul> wrapper around multiple ReadMoreItem_A components. This wrapper then permits the developer to style the entire group, especially with positioning utility classes.

In additon, a HyperScript in the group component listens for the closeOtherReadMores event and then optionally ssends the closeUp event to all other ReadMoreItems according to the boolean closeSiblings prop.

Props

interface Props {
closeSiblings?: boolean
styles?: string
}
const {
closeSiblings = true,
styles
} = Astro.props
closeSiblings

This boolean value controls whether or not the ReadMoreGroup_A component should close other ReadMoreItems before the sending control is opened. The default is true.

styles

This prop allows the developer to pass in any Tailwind CSS utility classes to style the ReadMoreGroup_A component. Most useful classes for the group component are positioning, margins, padding, and the like.

Slots

default slot

Multiple ReadMoreItem_A components can be passed into the default slot of the ReadMoreGroup_A component.

Events

closeOtherReadMores

This event is fired by any ReadMoreItem_A component during it’s openUp event. A HyperScript in the ReadMoreGroup_A listens for this event and optionally closes all other ReadMoreItems within the group.

Code

Explore the Tabs below to examinea all the code for the ReadMoreGroup_A component. Use the Everything tab to copy/paste into your project.

interface Props {
closeSiblings?: boolean //sibling ReadMoreItems will be closed
styles?: string //tw classes to position/style the entire group
}
const {
closeSiblings = true,
styles
} = Astro.props

ReadMoreItem_A

The heart of this control is in the ReadMoreItem_A component. This component provides a single <li> wrapper around both the introductory text and the hidden content.

The introductory text is passed into the components default slot. This strategy allows you to put any amount and any styling you wish into the introductory text.

Hidden content is lazy-loaded using the fetch command in the accompanying HyperScript. An id and path values are passed as props. For this example, the id prop also serves the file slug which is concatenated to the path to build a valid route to the backend api endpoint and retrieve the correct hypermedia response.

Review the Scripts tab in the Code section below for the HyperScript used to implement lazy-loading of the hidden content.

Props

interface Props {
id:string
path?:string
caption?:string
altCaption?:string
maxHeight?:number
duration?:string
readMoreStyles?:string
buttonStyles?:string
iconStyles?:string
bodyStyles?:string
}
id

Each item should be given a unique id which serves the dual purpose of preventing name collisions with other ReadMoreItem’s as well as providing the slug for the file path where the content will be fetched. If you are using a backend database, the id could also represent the record identifier.

path

This prop carries the route to the api endpoint where the content will be fetched. String concatenation in the HyperScript builds the final route using both id and the path values.

caption

The string value passed in the caption prop is the initial text content of the trigger button when the body content is hidden. The default caption is ‘Read More…‘

altCaption

The string value passed in the altCaption prop is displayed as the text content of the trigger button when the body content is unhidden. This text should encourage the user to click again to hide the content.

The default altCaption is ‘Read Less…‘

maxHeight

The body content of the ReadMore is clipped to a fixed height beyond which the content scrolls vertically.

The default value is 500. The underlying units used by the script code is pixels. This prop accepts a number so a mathematical comparison can be performed to determine if the calculated height of the body content exceeds the maxHeight value.

duration

This props accepts a string indicating the timing of the transition for showing and hiding the body content of the ReadMoreItem.

readMoreStyles

The trigger button and twirl-icon are wrapped by a single flex container. You can set the background, height, hover, etc. styles by sending in additional TW classes to the readMoreStyles prop.

buttonStyles

Use this prop to separately style the trigger button.

iconStyles

Use this prop to separately style the twirl-icon.

bodyStyles

This prop can be used to provide general styles to the container in which the body content is displayed. Some of those styles, like text-color will then be inherited by the actual text of the content, as per usual with CSS inheritance.

Slots

default

The default slot is used to place the introductory text of each ReadMoreItem. You can customize the styling of the introductory text by applying TW classes to these elements.

twirl-icon

The default content of the twirl-icon slot is a down-caret SVG. You can override this by placing a custom SVG with a slot='twirl-icon' attribute between the opening and closing ReadMoreItem_A tags.

twirl-icon slot
<svg id={`twirl-icon-${id}`} class={tw.icon} fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 24a12 12 0 1 1 12-12 12.013 12.013 0 0 1-12 12zm0-22a10 10 0 1 0 10 10A10.011 10.011 0 0 0 12 2z"/><path d="m12 18.414-4.707-4.707 1.414-1.414L12 15.586l3.293-3.293 1.414 1.414L12 18.414z"/>
<path d="M11 6h2v11h-2z"/>
</svg>

In order for the rotation behavior to work, you must also encode the id of the svg exactly as twirl-icon-${id} (enclosed in back-ticks). This identifier will then be picked up by the Hyperscript and the rotate-180 class toggled when the openUp or closeUp events are triggered.

Events

openUp

The ReadMoreItem_A component listens for the openUp event and then recalculates the height of the body <div>.

Also the .rotate-180 class is added to the twirl icon and the trigger button text is replaced with the string passed into the altCaption prop.

closeUp

The ReadMoreItem_A component listens for the closeUp event then sets the body height to zero, removes the .rotate-180 class from the twirl icon, and replaces the trigger button text with the string passed into the caption prop.

Transitions

The ReadMore transition effect is implemented by setting the initial height of the .body to zero. In addition to TW utility classes a body class is added to the div containing the lazy-loaded additional content. This strategy keeps the div within the DOM so that the script can access the offsetHeight property of each child.

The calcHeight() function in the script recalculates the final height based on the offsetHeight of each child within the div.body.

A style attribute with transition timing function is added to the div.body. When the script sets a new value to the height property of the div.body, this transition is triggered behind the scenes.

<div class={tw.body} style=`transition: all ${duration} ease-out`></div>

You can customize the timing using the duration prop. Using string interpolation via back-ticks allows the value of this prop to be accessed by the script.

Code

Below you will find all the code segments for the ReadMoreItem_A component.

To use this component in your Astro project, copy/paste the contents of the Everything tab into a .astro file in your src folder.

The ReadMoreItem_A component is meant to be used in conjunction with the parent component ReadMoreGroup_A.

interface Props {
id:string //a unique identifier which doubles as the file 'slug' for loading content from api route
path?:string //the path to the api route which will return the content for this ReadMore item
caption?:string //the caption for the trigger button
altCaption?:string //the caption for the trigger button when the content is expanded
maxHeight?:number //maximum open height in px (after which the body content scrolls vertically)
duration?:string //the duration of the height transition
readMoreStyles?:string //override or add TW classes to the readMore flex container
buttonStyles?:string //override or add TW classes to the trigger button
iconStyles?:string //override or add TW classes to the twirl icon
bodyStyles?:string //override or add TW classes to the body element
}
const {
id,
path,
caption = 'Read More...',
altCaption = 'Read Less...',
maxHeight = 500,
duration='400ms',
readMoreStyles,
buttonStyles,
iconStyles,
bodyStyles
} = Astro.props

Usage

The most common use case for ReadMore controls is on blog sites. Each blog post is introduced by a short title section, including a summary, author, and posting date. Clicking the Read More… caption activates the transition to expose the full content of the blog post. FAQs and other stacked article formats can also make use of the ReadMore control.