Veto
Components

Cron

A visual builder for a cron expression, read back in plain language, with the next few runs worked out.

Building a schedule

The tabs are the familiar phrasings of a schedule; each one writes the expression underneath. The expression stays visible and editable throughout - the builder is meant to teach cron, not hide it - and anything the tabs cannot say can still be typed by hand.

Everydays at

At 09:00, only on Monday, Tuesday, Wednesday, Thursday and Friday

Next runsMon 14 Sep 2026, 09:00Tue 15 Sep 2026, 09:00Wed 16 Sep 2026, 09:00Thu 17 Sep 2026, 09:00Fri 18 Sep 2026, 09:00
0 9 * * 1-5
razor
<VetoCron @bind-Value="_schedule" />

What the expression actually means

Every expression is read back as a sentence, and the next runs are worked out from it. Between them they catch the mistakes a cron expression is famous for - the schedule that looks daily and fires every minute, or the one that looks monthly and never fires at all.

* * * * * Every minute
*/15 * * * * Every 15 minutes
0 9 * * 1-5 At 09:00, only on Monday, Tuesday, Wednesday, Thursday and Friday
0 9,17 * * * At 09:00 and 17:00
0 0 1 * * At 00:00, on the 1st
0 0 1 1 * At 00:00, on the 1st, only in January
razor
@* The describer is usable on its own, without the builder: *@
@VetoCronDescriber.Describe("0 9 * * 1-5")
@* "At 09:00, only on Monday, Tuesday, Wednesday, Thursday and Friday" *@

@* So is the scheduler: *@
VetoCronExpression.TryParse("0 9 * * 1-5", out var cron, out var error);
var next = VetoCronSchedule.Next(cron, DateTime.Now, 5);

The day trap

When both day-of-month and day-of-week are restricted, cron fires when EITHER matches - not both. It surprises nearly everyone, so the builder says so rather than letting the expression quietly mean something else. Notice the next runs: the 1st, and every Monday.

Everydays at

At 00:00, on the 1st or only on Monday

Both day fields are set, so this fires when either matches - not only when both do.

Next runsMon 14 Sep 2026, 00:00Mon 21 Sep 2026, 00:00Mon 28 Sep 2026, 00:00Thu 01 Oct 2026, 00:00Mon 05 Oct 2026, 00:00
razor
@* Fires on the 1st AND on every Monday - not only on a Monday the 1st. *@
<VetoCron Value="0 0 1 * MON" ShowModes="false" />

When it will not parse

The error names the field that is wrong rather than rejecting the whole expression, because the field is what you have to go and fix.

Everydays at
razor
<VetoCron Value="0 99 * * *" />
@* "Hours: '99' is outside 0-23." *@

Other languages

The built-in describer is English only, deliberately: Veto ships with no package dependencies, and one sentence is not the feature to break that for. Describer is the seam. Hand it CronExpressionDescriptor - which the docs site references, and your project would too - and you get its two dozen languages without the library ever depending on it.

Everydays at

At 09:00, Monday through Friday

razor
@* In your project, not in Veto: *@
@using CronExpressionDescriptor

@foreach (var language in new[] { "en", "fr", "de", "es" })
{
    var code = language;
    <VetoButton OnClick="() => _locale = code">@code</VetoButton>
}

<VetoCron @bind-Value="_schedule" Describer="Describe" />

@code {
    private string _schedule = "0 9 * * 1-5";
    private string _locale = "en";

    private string? Describe(string expression)
    {
        try
        {
            return ExpressionDescriptor.GetDescription(expression, new Options
            {
                Locale = _locale,
                Use24HourTimeFormat = true,
                ThrowExceptionOnParseError = true,
            });
        }
        catch
        {
            return null;   // fall back to the built-in describer
        }
    }
}

Fewer tabs, or none

AllowedModes narrows the builder to the shapes a particular schedule is allowed to take, and ShowModes false leaves just the expression, its description and the runs - which is what you want when the component is reviewing an expression rather than building one.

On
at

At 02:30, only on Sunday

Next runsSun 13 Sep 2026, 02:30Sun 20 Sep 2026, 02:30Sun 27 Sep 2026, 02:30

Backups may run daily or weekly.

Everydays at

Every 10 minutes

Next runsSat 12 Sep 2026, 08:10Sat 12 Sep 2026, 08:20Sat 12 Sep 2026, 08:30
razor
<VetoCron @bind-Value="_backup"
          Label="Backup schedule"
          AllowedModes="[VetoCronMode.Daily, VetoCronMode.Weekly, VetoCronMode.Advanced]"
          InitialMode="VetoCronMode.Weekly" />

@* Reviewing a schedule rather than building one: *@
<VetoCron Value="*/10 * * * *" ShowModes="false" ShowExpression="false" />

Without the boxes

Bordered false drops every border - the box around the builder and the box around each control inside it - leaving the schedule reading as a sentence on the page rather than as a form. The focus ring stays: the borders were decoration, the ring is how a keyboard user knows where they are.

On
at

At 07:00, only on Monday, Wednesday and Friday

Next runsMon 14 Sep 2026, 07:00Wed 16 Sep 2026, 07:00Fri 18 Sep 2026, 07:00
razor
<VetoCron @bind-Value="_schedule" Bordered="false" />

Disabled and read-only

Read-only still shows the description and the runs - which is the point of showing a schedule nobody may edit.

Everydays at

At 03:00, only on Sunday

Next runsSun 13 Sep 2026, 03:00Sun 20 Sep 2026, 03:00
Everydays at

At 03:00, only on Sunday

razor
<VetoCron Value="0 3 * * 0" ReadOnly="true" ShowModes="false" />
<VetoCron Value="0 3 * * 0" Disabled="true" />

Parameters

Parameter Type Default Description
Value string "0 9 * * *" The cron expression. Use @bind-Value.
Describer Func<string, string?>? null Turns an expression into a sentence. Null uses the built-in English describer; return null to fall back to it.
AllowedModes IReadOnlyList<VetoCronMode>? null Which tabs are offered. Null offers all seven.
InitialMode VetoCronMode Daily The tab shown first.
ShowModes / ShowExpression / ShowDescription / ShowNextRuns bool true Which parts are shown.
NextRunCount int 5 How many upcoming runs to list.
NextRunFormat string "ddd dd MMM yyyy, HH:mm" How each run is formatted.
Now DateTime? null The moment the runs count from. Set it to pin the list for a screenshot or a test.
Culture CultureInfo? null The culture the runs are formatted in.
IncludeSeconds bool false Parse the expression as six fields, seconds first.
Label / HelperText string? null Text above and below the builder.
Disabled / ReadOnly bool false The usual states. Read-only still shows the description and the runs.
Size VetoSize Medium ExtraSmall, Small, Medium, Large or ExtraLarge.
Bordered bool true False drops every border - the builder's box and each control's - keeping the focus ring.
OnValidChanged EventCallback<VetoCronExpression> — Raised when the expression changes and parses.
IsValid / Expression bool / VetoCronExpression? — Read-only: whether the expression parses, and its parsed form.
Something went wrong. Reload 🗙