ForgeWP

Solution: Featured Story Section

A complete category-filtered story block with per-post data bindings and an image fallback.

This solution completes Build a Featured Story Section. It adds a new block alongside the post listing from the recipe.

Complete block

src/blocks/FeaturedStory.tsx
1import {
2  defineBlock,
3  WpEditable,
4  WpImage,
5  WpQueryLoop,
6  useWpExcerpt,
7  useWpFeaturedImage,
8  useWpPermalink,
9  useWpTitle,
10} from "@forgewp/react"
11
12function FeaturedCard() {
13  return (
14    <article className="grid overflow-hidden border border-slate-200 bg-white md:grid-cols-2">
15      <WpImage
16        src={useWpFeaturedImage() || "/assets/story-placeholder.svg"}
17        alt=""
18        className="aspect-video h-full w-full object-cover"
19      />
20      <div className="flex flex-col justify-center p-6 md:p-10">
21        <p className="text-sm font-semibold uppercase tracking-wide text-slate-500">
22          Featured story
23        </p>
24        <h3 className="mt-3 text-2xl font-bold text-slate-950">
25          <a href={useWpPermalink()} className="underline-offset-4 hover:underline">
26            {useWpTitle()}
27          </a>
28        </h3>
29        <p className="mt-4 text-base leading-relaxed text-slate-600">
30          {useWpExcerpt()}
31        </p>
32      </div>
33    </article>
34  )
35}
36
37export default defineBlock({
38  name: "featured-story",
39  title: "Featured Story",
40  category: "widgets",
41  icon: "star-filled",
42  attributes: {
43    heading: { type: "string", label: "Section heading", default: "Featured story" },
44  },
45  edit: ({ attributes, setAttributes }) => (
46    <section className="bg-slate-50 px-6 py-16">
47      <div className="mx-auto max-w-6xl">
48        <WpEditable
49          tagName="h2"
50          value={attributes.heading}
51          onChange={(value) => setAttributes({ heading: value })}
52          className="mb-8 text-3xl font-bold text-slate-950"
53        />
54        <WpQueryLoop postType="post" categoryName="featured" postsPerPage={1} orderby="date" order="DESC">
55          <FeaturedCard />
56        </WpQueryLoop>
57      </div>
58    </section>
59  ),
60  save: ({ attributes }) => (
61    <section className="bg-slate-50 px-6 py-16">
62      <div className="mx-auto max-w-6xl">
63        <WpEditable
64          tagName="h2"
65          value={attributes.heading}
66          className="mb-8 text-3xl font-bold text-slate-950"
67        />
68        <WpQueryLoop postType="post" categoryName="featured" postsPerPage={1} orderby="date" order="DESC">
69          <FeaturedCard />
70        </WpQueryLoop>
71      </div>
72    </section>
73  ),
74})

Understand the selection

Table · 2 columns
Query prop
Purpose
postType="post"
Read standard WordPress Posts
categoryName="featured"
Match the category slug featured
postsPerPage={1}
Display one result
orderby="date"
Compare publication dates
order="DESC"
Choose the newest matching Post first

FeaturedCard reads the current query result. Its title and image are Post data; the section's heading is block data. The same child component is used in both views, keeping the card layout in one place.

The thumbnail is decorative alongside the linked story title in this example, so it uses an empty alt. The image source falls back to public/assets/story-placeholder.svg, the theme asset created in the recipe. Keep that file in your project when using this solution. For another design, choose alternative text and placeholder content appropriate to the information the image conveys.

Verify the result

Theme terminal: check and export the solution
pnpm typecheck
pnpm forgewp export --validate --strict
  1. Confirm the test site has a category with slug featured and two published Posts assigned to it.
  2. Install the updated theme, insert Featured Story on a content-rendering Page, and save.
  3. Compare the displayed story with the publication dates of matching Posts.
  4. Test the no-image placeholder, then add a featured image to that Post and refresh relevant caches.
  5. Change the Post title and the block's section heading independently, save, and reload.
  6. Place this block above Latest Posts and verify both sections' titles, links, and data.
  7. Check narrow and wide screens.

If no published Posts match, the section heading remains and the query renders no cards. Local placeholder content is for designing the layout; use the visitor page for live selection and empty-result checks.

See Loops, Custom Post Types & Queries for more query options, or return to Build a Latest Posts Block.