Veto
Components

Blink text

Text printed a character at a time, with a blinking cursor and longer pauses at punctuation. Feed it a finished string or a stream.

Typing

A full paragraph, so the pauses are visible at work: longer at a full stop than at a comma, which is what makes it read like speech rather than a ticker. The text below it does not move as the passage types, because PreserveSpace holds the finished footprint. SkipOnClick lets an impatient reader click the text to finish it.

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur; excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

Typing…

This paragraph sits below the one being typed, and stays exactly where it is.

razor
<VetoBlinkText Text="Initialising the component library."
               CharactersPerSecond="34"
               OnCompleted="OnDone" />

<VetoButton OnClick="Restart">Run it again</VetoButton>

@code {
    private int _run;
    private bool _done;

    // Changing the text is what restarts the reveal.
    private void Restart() { _run++; _done = false; }

    private void OnDone() => _done = true;
}

Scramble

A few glyphs flicker ahead of the cursor before settling into the real characters - the effect the falling-code screens are known for. The scramble is never longer than the text still to come, so it cannot imply more than there is.

Wake up, Neo... The Matrix has you.

razor
<VetoBlinkText Text="Wake up, Neo... The Matrix has you."
               CharactersPerSecond="16"
               Scramble="true"
               Loop="true"
               CursorCharacter="_" />

From a stream

Give it an IAsyncEnumerable and chunks are appended as they arrive, then drained at the reveal rate. Text from a slow or uneven source reads at a steady pace instead of landing in lurches - which is the difference between a model's output looking considered and looking stuck.

Waiting…
razor
<VetoBlinkText Stream="_stream" CharactersPerSecond="60" PreserveSpace="false" />

<VetoButton OnClick="Start">Start streaming</VetoButton>

@code {
    private IAsyncEnumerable<string>? _stream;

    private void Start() => _stream = Client.StreamAsync(prompt);
}

Where it pauses

PauseOn maps a character to extra milliseconds. Pass your own to pause on anything - a pipe between fields, a slash in a path, a full stop you want to linger on.

scanning | 12 hosts | 3 open ports | done

By word instead of by character, for longer passages:

Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos.

razor
<VetoBlinkText Text="scanning | 12 hosts | 3 open ports | done"
               CharactersPerSecond="70"
               PauseOn="@(new Dictionary<char, int> { ['|'] = 600 })"
               Loop="true" />

<VetoBlinkText Text="One word at a time reads faster."
               ByWord="true"
               CharactersPerSecond="7" />

What a screen reader gets

The whole text, once. The animated copy is hidden from assistive technology and a clipped copy of the finished text sits alongside it - announcing a character at a time would be unusable. Under prefers-reduced-motion the text appears complete and the cursor stops blinking: the motion is the decoration, the text is the content.

PreserveSpace holds the finished text's footprint while it types, so a paragraph does not reflow line by line and shove the page around beneath it. It needs a known length, so it applies to Text rather than Stream.

Parameters

Parameter Type Default Description
Text string? null The text to reveal. Changing it restarts the reveal.
Stream IAsyncEnumerable<string>? null Chunks appended as they arrive, drained at the reveal rate.
CharactersPerSecond double 45 How fast the text appears.
ByWord bool false Reveal whole words rather than single characters.
PauseOn IReadOnlyDictionary<char, int> commas and sentence ends Extra milliseconds after particular characters.
StartDelay int 0 Milliseconds to wait before starting.
Cursor / CursorCharacter bool / string true / "▋" The blinking cursor.
CursorWhenComplete bool false Whether the cursor stays once finished.
Scramble bool false Flicker random glyphs ahead of the cursor.
ScrambleLength int 3 How many glyphs flicker.
ScrambleCharacters string katakana and digits The glyphs used while scrambling.
Loop / LoopDelay bool / int false / 1600 Start again once complete, after a hold.
PreserveSpace bool true Hold the finished text's space so the layout does not reflow. Text only.
Instant bool false Show the text immediately, skipping the reveal.
SkipOnClick bool false A click on the text finishes the reveal at once. Skip() does the same from code.
OnCompleted EventCallback — Raised once the whole text has been revealed - for a stream, once it has ended and been drained.
Something went wrong. Reload 🗙