Conversation
📝 WalkthroughWalkthroughAdds a new ShadSkeleton component with theme integration, exports, example/playground pages and routes, version/changelog/README updates, and widget tests for color and sizing. Theme data, base theme, and variants extended to support skeleton theming. Changes
Sequence Diagram(s)Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
lib/src/components/skeleton.dart (1)
161-187: Optional: reduce duplicated widget tree between static and animated paths.Both branches rebuild the same
SizedBox -> ClipRRectstructure. Extracting a tiny helper for the shaped box would simplify maintenance.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/src/components/skeleton.dart` around lines 161 - 187, The SizedBox->ClipRRect->ColoredBox tree is duplicated in the if/else; extract a small helper method (e.g., _shapedBox(Color color) or a private Widget _buildShapedBox(Color color)) that returns the SizedBox with widget.width, widget.height, ClipRRect(borderRadius: effectiveRadius) and ColoredBox(color: color), then call that helper from both the static branch (pass effectiveColor) and the AnimatedBuilder's builder (pass paintColor); keep references to opacityAnim and effectiveRadius/effectiveColor as-is so behavior is unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/src/components/skeleton.dart`:
- Around line 108-116: Normalize the computed pulse config before calling
_ensureAnimation: clamp minOp (the minPulseOpacity variable) into the [0.0, 1.0]
range, and if half (the pulseHalfDuration resolved from widget.pulseHalfDuration
or sk.pulseHalfDuration) is <= Duration.zero replace it with a sensible positive
default (e.g. const Duration(seconds:1) or a small positive Duration), then pass
the sanitized half, curve, and minOp into _ensureAnimation; reference the
variables half, curve, minOp and the _ensureAnimation call when making the
change.
- Around line 175-176: The use of Color.withValues (e.g., the paintColor
assignment that calls effectiveColor.withValues and computes alpha as
(effectiveColor.a * factor).clamp(...)) requires Flutter 3.27.0+; either raise
the package SDK constraint to >=3.27.0 in pubspec.yaml or replace these calls
with a compatible approach such as computing the new opacity and using
effectiveColor.withOpacity((effectiveColor.opacity * factor).clamp(0.0,1.0)) (or
equivalent conversion from effectiveColor.a) everywhere you see
effectiveColor.withValues to ensure runtime compatibility for older SDKs.
---
Nitpick comments:
In `@lib/src/components/skeleton.dart`:
- Around line 161-187: The SizedBox->ClipRRect->ColoredBox tree is duplicated in
the if/else; extract a small helper method (e.g., _shapedBox(Color color) or a
private Widget _buildShapedBox(Color color)) that returns the SizedBox with
widget.width, widget.height, ClipRRect(borderRadius: effectiveRadius) and
ColoredBox(color: color), then call that helper from both the static branch
(pass effectiveColor) and the AnimatedBuilder's builder (pass paintColor); keep
references to opacityAnim and effectiveRadius/effectiveColor as-is so behavior
is unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f451237c-d150-40dc-86ff-95a45dd6b4c2
📒 Files selected for processing (17)
CHANGELOG.mdREADME.mdexample/lib/main.dartexample/lib/pages/skeleton.dartlib/shadcn_ui.dartlib/src/components/skeleton.dartlib/src/theme/components/skeleton.dartlib/src/theme/components/skeleton.g.theme.dartlib/src/theme/data.dartlib/src/theme/data.g.theme.dartlib/src/theme/themes/base.dartlib/src/theme/themes/default_theme_no_secondary_border_variant.dartlib/src/theme/themes/default_theme_variant.dartplayground/lib/pages/skeleton.dartplayground/lib/router.dartpubspec.yamltest/src/components/skeleton_test.dart
There was a problem hiding this comment.
🧹 Nitpick comments (1)
lib/src/components/skeleton.dart (1)
177-193: Hoist static layout out ofAnimatedBuilderto reduce per-frame rebuilds.Only the fill color changes per tick; wrapping
SizedBox/ClipRRectoutside the builder avoids rebuilding static structure every frame.♻️ Suggested refactor
- core = AnimatedBuilder( - animation: opacityAnim, - builder: (context, child) { - final factor = opacityAnim.value; - final paintColor = effectiveColor.withOpacity( - (effectiveColor.opacity * factor).clamp(0.0, 1.0), - ); - return SizedBox( - width: widget.width, - height: widget.height, - child: ClipRRect( - borderRadius: effectiveRadius, - child: ColoredBox(color: paintColor), - ), - ); - }, - ); + core = SizedBox( + width: widget.width, + height: widget.height, + child: ClipRRect( + borderRadius: effectiveRadius, + child: AnimatedBuilder( + animation: opacityAnim, + builder: (context, child) { + final factor = opacityAnim.value; + final paintColor = effectiveColor.withOpacity( + (effectiveColor.opacity * factor).clamp(0.0, 1.0), + ); + return ColoredBox(color: paintColor); + }, + ), + ), + );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/src/components/skeleton.dart` around lines 177 - 193, The AnimatedBuilder currently rebuilds the whole layout (SizedBox, ClipRRect) every frame; hoist the static widgets out of the builder and only rebuild the color; e.g., create the SizedBox->ClipRRect structure once outside where core is assigned and pass a child into AnimatedBuilder (or use AnimatedWidget) so the builder only computes paintColor from opacityAnim/effectiveColor and returns a ColoredBox (or updates the child's color), referencing opacityAnim, effectiveColor, widget.width/widget.height, effectiveRadius, ClipRRect, and ColoredBox to locate the code to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@lib/src/components/skeleton.dart`:
- Around line 177-193: The AnimatedBuilder currently rebuilds the whole layout
(SizedBox, ClipRRect) every frame; hoist the static widgets out of the builder
and only rebuild the color; e.g., create the SizedBox->ClipRRect structure once
outside where core is assigned and pass a child into AnimatedBuilder (or use
AnimatedWidget) so the builder only computes paintColor from
opacityAnim/effectiveColor and returns a ColoredBox (or updates the child's
color), referencing opacityAnim, effectiveColor, widget.width/widget.height,
effectiveRadius, ClipRRect, and ColoredBox to locate the code to change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 011ff1db-0ffd-4bf8-9f6b-ea8a3caf0213
📒 Files selected for processing (1)
lib/src/components/skeleton.dart
nank1ro
left a comment
There was a problem hiding this comment.
One open question:
Have you considered https://pub.dev/packages/skeletonizer? Basically, the user doesn't have to replicate the visual design of the components manually, it's already handled by the library itself.
| AnimationController? _controller; | ||
| CurvedAnimation? _curved; | ||
| Animation<double>? _opacityFactor; | ||
| Duration? _lastHalfDuration; | ||
| Curve? _lastCurve; | ||
| double? _lastMinOpacity; |
There was a problem hiding this comment.
- you should use flutter_animate for animations, they are completely customizable by the user without changing the widget structure.
- make variables public, you're already inside a private class
_ShadSkeletonState
|
@nank1ro Interesting package, never heard of that before, seems like a pretty solid solution for this type of problem yes. Do you rather promote the use of that instead of a dedicated skeleton widget in this library? Doesn't seem like a bad idea, but if so I would suggest updating the readme / docs to refer to that, alternatively add it as a dependency with a local wrapper, include a default styling to match the shadcn style (and configurability via the theme). |
the library already has external dependencies, see them at https://mariuti.com/flutter-shadcn-ui/packages/ |
Changes
Adds the skeleton widget. The widget mimics the React shadcn component as closely as possible, using the same animation curve, timing and coloring.
Demo videos:
Left React component
Right Flutter widget
Note: The background color of the ShadCard in dark mode does not match, but is unrelated to this PR
Screen.Recording.2026-03-25.at.23.02.55.mov
Screen.Recording.2026-03-25.at.23.03.15.mov
Testing this PR
To try this branch, add the following to your
pubspec.yaml:Pre-launch Checklist
///).0.18.0and you introduced a breaking change or a new feature, bump it to0.19.0, if you just added a fix or a chore bump it to0.18.1.CHANGELOG.mdfile with a summary of changes made following the format already used.Summary by CodeRabbit
New Features
Documentation
Examples
Tests
Chores