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
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
Query prop Purpose postType="post"Read standard WordPress Posts categoryName="featured"Match the category slug featuredpostsPerPage={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
pnpm typecheck
pnpm forgewp export --validate --strict- Confirm the test site has a category with slug
featuredand two published Posts assigned to it. - Install the updated theme, insert Featured Story on a content-rendering Page, and save.
- Compare the displayed story with the publication dates of matching Posts.
- Test the no-image placeholder, then add a featured image to that Post and refresh relevant caches.
- Change the Post title and the block's section heading independently, save, and reload.
- Place this block above Latest Posts and verify both sections' titles, links, and data.
- 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.