Show more
Clamps long content to a few lines, and only offers to expand what actually overflows.
pnpm dlx shadcn-svelte@latest add https://coral.imlargo.dev/r/kit-show-more.jsonnpx shadcn-svelte@latest add https://coral.imlargo.dev/r/kit-show-more.jsonbunx --bun shadcn-svelte@latest add https://coral.imlargo.dev/r/kit-show-more.jsonyarn dlx shadcn-svelte@latest add https://coral.imlargo.dev/r/kit-show-more.jsonA description, an incident note, release notes: clamp it with line-clamp-3 and add a "Show more"
button, and the button shows up under text that already fits, the clamp does nothing to a paragraph
followed by a list, and tabbing onto a link inside the hidden part puts focus somewhere nobody can
see.
Between 09:14 and 10:02 UTC, around 8% of builds in the Production environment failed with a 503 from the artifact store. The cause was a connection pool that was never resized after last week's database upgrade. Builds queued during the window were replayed automatically once the pool was widened, so no deployment was lost. Adding a pool-size check to the upgrade checklist is tracked as a follow-up on the incident review.
A short note that fits entirely: no button shows up here.
<script lang="ts">
import ShowMore from '#lib/components/coral/kit/show-more/show-more.svelte';
</script>
<div class="flex w-96 flex-col gap-6 text-sm">
<ShowMore lines={3} moreLabel="Show more" lessLabel="Show less">
<p>
Between 09:14 and 10:02 UTC, around 8% of builds in the Production environment failed with a
503 from the artifact store. The cause was a connection pool that was never resized after last
week's database upgrade. Builds queued during the window were replayed automatically once the
pool was widened, so no deployment was lost. Adding a pool-size check to the upgrade checklist
is tracked as a follow-up on the incident review.
</p>
</ShowMore>
<ShowMore lines={3} moreLabel="Show more" lessLabel="Show less">
<p>A short note that fits entirely: no button shows up here.</p>
</ShowMore>
</div>What Coral adds
- A toggle only when there is more. Overflow is measured, and re-measured as the width or the content changes. Short content gets no button.
- Any markup: clipped by height in
lhunits (the content's own line height), so paragraphs, lists and inline elements are cut alike.line-clamponly counts one run of inline text. - Focus never lands out of sight. Tabbing onto a link in the clipped part expands the content.
- Collapsing keeps your place. If the top of a long block has scrolled away, collapsing brings it back into view instead of leaving the reader somewhere below where the content now ends.
- Wired up. The toggle carries
aria-expandedandaria-controls.
Rich content, custom toggle
What's new in version 2.4:
- Deploy previews on every pull request.
- Saved filters per project.
- API keys scoped to a single environment.
- Configurable webhook notifications.
- View full changelog
Tabbing to the hidden link also expands it.
<script lang="ts">
import ShowMore from '#lib/components/coral/kit/show-more/show-more.svelte';
import { Button } from '#lib/components/ui/button/index.js';
let expanded = $state(false);
</script>
<div class="w-96 text-sm">
<ShowMore lines={4} bind:expanded>
<p class="mb-2">What's new in version 2.4:</p>
<ul class="list-disc ps-5">
<li>Deploy previews on every pull request.</li>
<li>Saved filters per project.</li>
<li>API keys scoped to a single environment.</li>
<li>Configurable webhook notifications.</li>
<li><a class="underline" href="#main-content">View full changelog</a></li>
</ul>
{#snippet toggle({ props, expanded })}
<Button {...props} variant="outline" size="sm">
{expanded ? 'Hide changelog' : 'View changelog'}
</Button>
{/snippet}
</ShowMore>
<p class="mt-2 text-muted-foreground">Tabbing to the hidden link also expands it.</p>
</div>The toggle snippet receives props to spread onto your own button (they carry the ARIA wiring and
the click handler), plus expanded and toggle.
Import
<script lang="ts">
import ShowMore from '#lib/components/coral/kit/show-more/show-more.svelte';
</script>Props
Everything a <div> accepts stays available on the root. On top of that:
| Prop | Type | Default | Description |
|---|---|---|---|
lines | number | 3 | Lines shown while collapsed. |
expanded | boolean | false | Bindable. |
onexpandedchange | (expanded: boolean) => void | - | The reader toggled it. |
moreLabel | string | Show more | Default toggle, collapsed. |
lessLabel | string | Show less | Default toggle, expanded. |
class | string | - | Merged onto the root. |
contentClass | string | - | Merged onto the clipped region. |
children | Snippet | - | The content. Required. |
toggle | Snippet<[{ props, expanded, toggle }]> | - | Replaces the toggle. |
Accessibility
Clipped content is still in the accessibility tree, so a screen reader reads all of it: clamping is a visual convenience, not a way to hide text. That is also why the button's label does not need to describe what is hidden.
A height cut can slice an image in half at the boundary. That is the trade for clamping any markup, and the right one for text; for a gallery, show a count of items instead.