Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 38 additions & 14 deletions .agents/skills/contribute-blog/references/post-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ description: >-
A short, plain-text summary that stands on its own.
publishDate: YYYY-MM-DD
author: author-id
image: images/card-image.webp
coverImage:
alt: "Descriptive alt text, or empty for decorative."
caption: "Optional caption."
url: images/hero.webp
category: deep-dive
layout: blog
---
Expand Down Expand Up @@ -61,32 +64,52 @@ layout: blog
`layout` (required)
: Must be `blog`.

`image` (optional)
: Post-relative path (for example, `images/hero.webp`) to the card and
default social preview image.
`coverImage` (optional)
: Post-relative hero/cover image that supplies the `/blog` card thumbnail,
social preview, and optional in-body rendering before the first paragraph.
Contains `url` (path to image), `alt` (alternative text; empty string `""`
for decorative images; required when `display` is `true`), optional `caption`,
and optional `display` (boolean; defaults to `true`).
Set `display: false` if you want the card thumbnail and social preview
without automatically rendering the image at the top of the post (for
example, if you prefer to place the image elsewhere in the body with
`<DashImage>` or omit in-body display).

`socialImage` (optional)
: Static image override (WebP or PNG under 5 MB) for social sharing metadata
when `image` is animated or exceeds 5 MB.
when `coverImage` is animated or exceeds 5 MB.

## Card and social images

`image` is optional in the data model but
recommended when suitable artwork exists.
It supplies the blog card image and social preview.
The body layout doesn't render `image` automatically.
If the hero image should appear in the post's body,
include it explicitly in the Markdown content with `<DashImage>`.
`coverImage` is optional but recommended when suitable artwork exists.
It supplies the default card image and social preview (unless overridden
by `socialImage`), and automatically renders at the top of the post body
when `display` is `true` (the default).
If `coverImage` is omitted, no image is rendered at the top of the post body,
and the `/blog` card displays without a thumbnail.

To supply a card and social preview image without rendering it at the top
of the post, set `display: false`:

```yaml
coverImage:
url: images/workflow.webp
display: false
```

You can then embed the image elsewhere in the article body using `<DashImage>`.

Use post-local paths such as `images/hero.webp`.
Prefer optimized WebP for new static raster artwork when quality permits.
Don't upscale sources or commit unused variants.

Set `socialImage` when `image` is animated (such as a GIF),
Set `socialImage` when `coverImage` is animated (such as a GIF),
larger than 5 MB, or otherwise unsuitable for social previews:

```yaml
image: images/animated-hero.gif
coverImage:
alt: "Animated hero diagram"
url: images/animated-hero.gif
socialImage: images/social-cover.webp
```

Expand All @@ -96,7 +119,8 @@ Use a static WebP or PNG under 5 MB for `socialImage`.

### Images

Use `<DashImage>` for post-local figures, asset processing, or captions:
Hero images at the top of a post are defined using `coverImage` in frontmatter.
For images within the article body, use `<DashImage>`:

```markdown
<DashImage figure src="images/workflow.webp" alt="Diagram showing requests moving from the Flutter app through the API to the cache" caption="Requests move through the API before reaching the cache." />
Expand Down
1 change: 1 addition & 0 deletions packages/site_shared/lib/blog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

export 'components/blog/client/blog_categories.dart' show BlogCategory;
export 'src/blog/atom_feed.dart';
export 'src/blog/models.dart';
3 changes: 2 additions & 1 deletion packages/site_shared/lib/components/blog/blog_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ class BlogCard extends StatelessComponent {
div(classes: 'blog-card-image', [
img(
src: postImage,
alt: post.title,
// The link already includes the post title below the image.
alt: '',
loading: switch (priority) {
.featured => MediaLoading.eager,
.high => MediaLoading.lazy,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import 'package:jaspr_content/jaspr_content.dart';
import '../../blog.dart';
import 'blog_card.dart';
import 'blog_index.dart';
import 'client/blog_categories.dart';

class BlogNextPosts extends StatelessComponent {
const BlogNextPosts({
Expand Down
57 changes: 57 additions & 0 deletions packages/site_shared/lib/src/blog/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,48 @@ extension type Post(Map<String, Object?> data) {
);
}

if (data['coverImage'] case final coverData?) {
if (coverData is Map && coverData.isEmpty) {
// Treat an empty map as absent.
} else if (coverData is! Map<String, Object?> ||
coverData['url'] is! String ||
(coverData['url'] as String).isEmpty) {
throw ArgumentError(
'Invalid "coverImage" metadata$sourceDescription. '
'Must include a non-empty "url" string.',
);
} else {
if (coverData['display'] != null && coverData['display'] is! bool) {
throw ArgumentError(
'Invalid "display" in "coverImage" metadata$sourceDescription. '
'Must be a boolean if provided.',
);
}
final display = coverData['display'] != false;
if (display && coverData['alt'] is! String) {
throw ArgumentError(
'Invalid "coverImage" metadata$sourceDescription. '
'Must include an "alt" string when displayed '
'(empty string for decorative).',
);
}
if (!display &&
coverData['alt'] != null &&
coverData['alt'] is! String) {
throw ArgumentError(
'Invalid "alt" in "coverImage" metadata$sourceDescription. '
'Must be a string if provided.',
);
}
if (coverData['caption'] != null && coverData['caption'] is! String) {
throw ArgumentError(
'Invalid "caption" in "coverImage" metadata$sourceDescription. '
'Must be a string if provided.',
);
}
}
}

return post;
}

Expand All @@ -47,7 +89,15 @@ extension type Post(Map<String, Object?> data) {

String get title => data['title'] as String;
String get description => data['description'] as String;

/// The card image URL, derived from [coverImage] and
/// resolved by the page's data loaders.
String? get image => data['image'] as String?;
CoverImage? get coverImage => switch (data['coverImage']) {
final Map<String, Object?> coverImageData when coverImageData.isNotEmpty =>
CoverImage(coverImageData),
_ => null,
};
String get publishDate => data['publishDate'] as String;

DateTime get dateObject => DateTime.parse(publishDate);
Expand All @@ -65,6 +115,13 @@ extension type Post(Map<String, Object?> data) {
String get category => data['category'] as String;
}

extension type CoverImage(Map<String, Object?> data) {
String get url => data['url'] as String;
String? get alt => data['alt'] as String?;
String? get caption => data['caption'] as String?;
bool get display => data['display'] != false;
}

extension type Author(Map<String, Object?> data) {
String get name => data['name'] as String;
String? get image => data['image'] as String?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ description: >-
Why I stopped planning and started building.
publishDate: 2026-03-05
author: lamek
image: images/18qm-mMi6Hod2jBw1_2nNEw.webp
coverImage:
alt: ""
caption: "Dash having fun building Flutter apps."
url: images/18qm-mMi6Hod2jBw1_2nNEw.webp
category: case-study
layout: blog
---

<DashImage figure src="images/18qm-mMi6Hod2jBw1_2nNEw.webp" alt="Dash having fun building Flutter apps." caption="Dash having fun building Flutter apps." />

Hi, I’m Kevin Lamenzo! I recently joined the Dart and Flutter teams (🎉), and this January I set out on a quest: build 20 apps in 20 days using Flutter and [Antigravity](https://antigravity.google/). But why? First, the cost of curiosity has officially hit zero. You can go from idea to app in 10 minutes. Second, as a new member of the team I wanted to learn about the framework by building something. Last, and most importantly, I wanted to test the theory that in 2026, we all can be builders now.

I’m here to share what I learned from the trenches. If you take only one thing from this post, let it be this: stop reading, and go build something.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ description: >-
We added new GMA Flutter features based on your feedback!
publishDate: 2023-05-10
author: timothyhoang
image: images/1PLQQUPqp_e0LmXTbAgb_iQ.webp
coverImage:
alt: ""
url: images/1PLQQUPqp_e0LmXTbAgb_iQ.webp
category: news
layout: blog
---

<DashImage figure src="images/1PLQQUPqp_e0LmXTbAgb_iQ.webp" />

As you build and grow your Flutter apps, you may want your apps to make money. You can monetize your apps in many ways. Introducing advertising would be one of the most common strategies. To introduce ads, you can use the [Google Mobile Ads](https://pub.dev/packages/google_mobile_ads) (GMA) Flutter plugin. The GMA plugin can place ads from AdMob, Google Ad Manager, or AdSense in your Flutter mobile app.

Feature and tooling requests have grown in step with GMA Flutter plugin usage. Based on your Ad Manager and AdMob survey responses, the plugin now supports:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ description: >-
across mobile, web, and desktop.
publishDate: 2019-05-08
author: timsneath
image: images/13XedGb879lTK2vbCP0uscA.webp
coverImage:
url: images/13XedGb879lTK2vbCP0uscA.webp
display: false
category: news
layout: blog
---
Expand Down
7 changes: 4 additions & 3 deletions sites/www/content/blog/a2ui-client-side-functions/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ description: >-
directly to Dart code running on a user's device.
publishDate: 2026-08-28
author: redbrogdon
image: images/A2UI.gif
coverImage:
alt: ""
caption: "Learn how to use client-side functions"
url: images/A2UI.gif
socialImage: images/A2UI_Still.png
category: deep-dive
layout: blog
---

<DashImage figure src="images/A2UI.gif" alt="Learn how to use client-side functions" caption="Learn how to use client-side functions" />

Generative UI (GenUI) is transforming how I approach UI
development in Flutter. Instead of hardcoding fixed screens
for every scenario, GenUI enables an AI agent to dynamically
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ description: >-
accessible to users of assistive technologies
publishDate: 2024-04-16
author: tomayac
image: images/0pF3_jPjWNcie3LH0.webp
coverImage:
url: images/0pF3_jPjWNcie3LH0.webp
display: false
category: deep-dive
layout: blog
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ description: >-
build experiences that are flexible, accessible, and expressive
publishDate: 2020-11-04
author: andremobilelabonte
image: images/1kcT41A5GvXp8XrjHjiuc4A.webp
coverImage:
url: images/1kcT41A5GvXp8XrjHjiuc4A.webp
display: false
category: news
layout: blog
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ description: >-
look beautiful on all devices
publishDate: 2023-01-25
author: leighajarett
image: images/1RA_cBWkp9GOQsK_H0-Dg8w.gif
coverImage:
url: images/1RA_cBWkp9GOQsK_H0-Dg8w.gif
display: false
category: case-study
layout: blog
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ description: >-
success stories announced at Google I/O 2024
publishDate: 2024-05-30
author: mit-mit
image: images/1--DvqdXSA38rPuqMK5c0tQ.webp
coverImage:
url: images/1--DvqdXSA38rPuqMK5c0tQ.webp
display: false
category: news
layout: blog
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ description: >-
respond to the system brightness setting.
publishDate: 2019-05-15
author: matthew-carroll
image: images/1wogDV86zrS8BNYI-8Th-TA.jpeg
coverImage:
alt: ""
url: images/1wogDV86zrS8BNYI-8Th-TA.jpeg
category: deep-dive
layout: blog
---

<DashImage figure src="images/1wogDV86zrS8BNYI-8Th-TA.jpeg" />

## Android Dark Theme in Flutter

A week ago at Google I/O 2019, the Android team [announced support in Android Q for Dark Theme](https://developer.android.com/preview/features/darktheme), a system-wide dark UI mode that is toggled by the user. This then begs the question, when will Flutter add support for this awesome new Android feature? Well, you might be surprised to find out that [Flutter added support for dark theme back in February](https://github.com/flutter/flutter/pull/26605)! Here’s how to use it.
Expand Down
4 changes: 3 additions & 1 deletion sites/www/content/blog/animation-deep-dive/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ description: >-
techniques from the Flutter Animations series.
publishDate: 2020-04-17
author: filiph
image: images/14bIqmJ8DQLSnlFtkN6bjaA.webp
coverImage:
url: images/14bIqmJ8DQLSnlFtkN6bjaA.webp
display: false
category: deep-dive
layout: blog
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ description: >-
reactive view frameworks like Flutter
publishDate: 2018-01-19
author: amir_h
image: images/10gdFpYTSo_TTCqd-XVcvYw.webp
coverImage:
url: images/10gdFpYTSo_TTCqd-XVcvYw.webp
display: false
category: deep-dive
layout: blog
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ description: >-
Create in XD and export to working Flutter code
publishDate: 2020-05-13
author: timsneath
image: images/0N-K68XqE0DBUV0EK.webp
coverImage:
url: images/0N-K68XqE0DBUV0EK.webp
display: false
category: news
layout: blog
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ description: >-
CodePen is adding support for Flutter
publishDate: 2020-04-15
author: zoeyfan
image: images/06wPqdCz_TzB2gAXr.webp
coverImage:
alt: ""
url: images/06wPqdCz_TzB2gAXr.webp
category: news
layout: blog
---

<DashImage figure src="images/06wPqdCz_TzB2gAXr.webp" />

Today we’re excited to announce that [CodePen](https://codepen.io), the leading social development environment for millions of front-end developers and designers, is adding [support for Flutter](https://codepen.io/flutter)! For web developers CodePen has long been a great place for sharing design explorations, new techniques, and ideas. Now with the introduction of Flutter, CodePen enables a new audience to learn, share and promote their creativity.

In the words of Alex Vazquez, one of the co-founders of CodePen:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ description: >-
Beginning the journey to migrate packages to a sound, safer state
publishDate: 2020-11-19
author: mit-mit
image: images/1kr-aQjGvnhY0JDCx1CGzRg.webp
coverImage:
url: images/1kr-aQjGvnhY0JDCx1CGzRg.webp
display: false
category: news
layout: blog
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ description: >-
new tooling, and Dart 2.7.
publishDate: 2019-12-11
author: csells
image: images/1sW6TueDcNzw806cjigLJaA.webp
coverImage:
url: images/1sW6TueDcNzw806cjigLJaA.webp
display: false
category: release
layout: blog
---
Expand Down
4 changes: 3 additions & 1 deletion sites/www/content/blog/announcing-flutter-1-17/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ description: >-
new Material components, new Network tracking tooling, and more!
publishDate: 2020-05-06
author: csells
image: images/14MIF2GwByNG1MJ7DNslb3Q.webp
coverImage:
url: images/14MIF2GwByNG1MJ7DNslb3Q.webp
display: false
category: release
layout: blog
---
Expand Down
Loading
Loading