mattsplat/readmore
mattsplat/readmore is a Laravel package for truncating long content and adding a customizable “Read more” link. Configure length limits and use Blade-friendly output to create clean excerpts for blog posts, article cards, and search results.
mattsplat/readmore is a Laravel Nova field that renders long text collapsed behind a "read more" toggle, expanding client-side without a page load. Version 2.2.0 is the first release to support Nova 5, so it's now viable on both Nova 4 and Nova 5 stacks.
Install and first use:
composer require mattsplat/readmorefields() method, typically for a text/longText column:use Mattsplat\Readmore\Readmore;
public function fields(Request $request)
{
return [
Readmore::make('Body'),
];
}
php artisan nova:asset mattsplat/readmore (or nova:publish) if the compiled bundle isn't picked up automatically, then hard-refresh the browser.Where to look first: the README for the field's truncation-length and toggle-label options, then dist/js/field.js if you need to confirm exactly what ships to the browser. Since 2.2.0 leaves the field code and compiled bundle untouched, anything you read about v2.1 behaviour still applies.
First real use case: a description or notes column that's too long for an index table but too useful to hide behind a detail view. Drop the field into fields() and it truncates in the index and expands in place.
Index vs detail separation. The common pattern is a collapsed preview on the index and the full value on the detail screen:
Readmore::make('Body')
->onlyOnIndex(),
Text::make('Body')
->onlyOnDetail(),
Keeping the toggle on the index is usually what you want — on a detail view the user has already committed to reading the record.
Reusing an existing attribute. If you'd rather not add a new column, resolve the field from an accessor so formatting (markdown → HTML, excerpt generation) stays in the model:
Readmore::make('Excerpt')
->resolveUsing(fn ($value, $resource) => Str::limit(strip_tags($resource->body), 300)),
Pairing with rich text. Because truncation happens in the browser, the field receives whatever string the model provides. Convert markdown or sanitise HTML in an accessor first, otherwise a cut can land mid-tag and produce broken markup.
Lenses and relations. The field is a normal Nova field, so it composes with Lens field sets and relation fields (HasMany::make(...)->fields(...)) without special handling. If a relation table gets crowded, put the read-more field on the related resource's index and keep the parent view to a short summary.
Consistency across resources. Register it once and reuse: a fields() helper or a base resource method returning Readmore::make('Body') with the same length/labels keeps collapse behaviour uniform, which matters more than it sounds — inconsistent truncation points look like bugs.
PHP 8.0 pins you to Nova 4. The laravel/nova constraint is ^4.0 || ^5.0, but Composer only resolves Nova 5 on PHP 8.1+. On PHP 8.0 you'll silently stay on Nova 4 — check composer show laravel/nova after upgrading Nova rather than assuming the constraint did what you wanted. This is the single most likely source of "I upgraded Nova but the field looks the same" confusion, since it isn't a field problem at all.
Truncation is client-side, so the full text still ships. The collapsed state is cosmetic: the entire column value goes into the Nova field payload and into the HTML. On a longText with kilobytes of content and 50 rows per page, that's real weight. Use onlyOnIndex() with a short accessor, or a separate excerpt column, rather than assuming the toggle saves bandwidth.
Not sortable or searchable on its own. Nova sorting and search operate on the underlying database column. A Readmore field wrapping a computed accessor can't be clicked to sort, and searching the resource won't hit the resolved value. If users need to sort by the column, keep the real column in the field chain and sort on it.
No bundle rebuild needed — but re-publish if you did. v2.2.0 explicitly leaves the compiled bundle unchanged, so there's nothing to rebuild. If you previously ran nova:publish and vendored the assets, re-run it after upgrading so you're not running a stale copy from an older tag.
Whitespace and multibyte text. Truncation counts characters as JavaScript sees them; text with emoji or CJK characters can cut at unexpected points, and long unbroken strings (URLs, hashes) won't wrap nicely. Where the content is user-generated, pre-truncate server-side with Str::limit() and treat the field as presentation only.
HTML in the value. If the column contains markup, strip it before the field sees it (strip_tags) unless you specifically want the read-more to reveal formatted HTML — the expanded/collapsed state won't repair unbalanced tags.
Extension points are thin by design. This is a display field, not a formatting pipeline. Anything beyond truncation length and labels belongs in an accessor or a custom Nova field — don't fight the package to add per-record behaviour it doesn't model.
Upgrade path is a no-op. Per UPGRADE.md, there is nothing to change going from 2.1 to 2.2 other than letting Composer resolve. Run composer update mattsplat/readmore laravel/nova together so the constraint solver can move both at once.
How can I help you explore Laravel packages today?