Guidely.Core
1.0.0
dotnet add package Guidely.Core --version 1.0.0
NuGet\Install-Package Guidely.Core -Version 1.0.0
<PackageReference Include="Guidely.Core" Version="1.0.0" />
<PackageVersion Include="Guidely.Core" Version="1.0.0" />
<PackageReference Include="Guidely.Core" />
paket add Guidely.Core --version 1.0.0
#r "nuget: Guidely.Core, 1.0.0"
#:package Guidely.Core@1.0.0
#addin nuget:?package=Guidely.Core&version=1.0.0
#tool nuget:?package=Guidely.Core&version=1.0.0
Guidely.Core
A powerful, framework-agnostic tutorial state machine library for .NET applications. Build interactive, step-by-step guided experiences with intelligent flow control.
Features
- Attribute-Driven Steps - Define tutorial steps as simple classes with
[TutorialStep],[TutorialTarget], and[AutoAdvanceOn]attributes - Conditional Transitions - Create dynamic tutorial flows that adapt based on application state using a fluent builder API
- Auto-Advance Triggers - Steps automatically advance when users complete actions (button clicks, form submissions, etc.)
- Context-Aware - Track application state with strongly-typed context records for intelligent decision-making
- Step Groups - Organize steps into logical sections (Introduction, Setup, Features, etc.) with ordering support
- Back Navigation - Navigate to previous steps within the current group
- Skippable Steps - Steps that require user action can show alternate content and a Next button when their goal is already achieved, with configurable skip targets
- Group-to-View Mapping - Map tutorial groups to application views/pages for intelligent restart-from-current-view behavior
- Persistence Ready - Implement
ITutorialPersistenceto save/restore tutorial progress across sessions - Framework Agnostic - Core logic works with any .NET UI framework (WPF, more coming soon)
- Dependency Injection - Support for Microsoft.Extensions.DependencyInjection
- Auto-Start on First Run - Optionally start the tutorial automatically when no persisted state exists
Use Cases
- Onboarding flows - Guide new users through initial setup and configuration
- Feature discovery - Introduce users to new or advanced features
- Interactive help - Provide contextual guidance within complex workflows
- Training modules - Build step-by-step training for enterprise applications
Installation
<PackageReference Include="Guidely.Core" Version="1.0.0" />
Or via .NET CLI:
dotnet add package Guidely.Core
Requirements
Framework Requirements
- .NET 10.0 or later
NuGet Dependencies
Guidely.Core has the following dependencies (automatically installed with the package):
- CommunityToolkit.Mvvm (8.0+) - MVVM infrastructure for ViewModels and reactive properties
- Microsoft.Extensions.DependencyInjection.Abstractions (8.0+) - Dependency injection support
UI Framework Integration
Guidely.Core is framework-agnostic and provides only the core tutorial state machine. To display tutorials in your application, you'll need a framework-specific UI package:
- WPF: Install
Guidely.WPFfor overlay controls and tutorial UI - MAUI/Avalonia: Coming soon
Quick Start
1. Define Your Context
Create a record inheriting from TutorialContextBase to track application state:
public record MyAppContext : TutorialContextBase
{
public bool HasCompletedSetup { get; init; }
public bool HasData { get; init; }
public bool IsFeatureEnabled { get; init; }
public string CurrentPage { get; init; } = string.Empty;
}
2. Define Triggers and Groups
public enum MyTrigger
{
SetupCompleted,
DataLoaded,
FeatureEnabled
}
public enum MyGroup
{
Introduction,
Setup,
Features,
Complete
}
3. Define Step IDs
Use static string constants for compile-time validation:
public static class StepIds
{
public const string Welcome = nameof(Welcome);
public const string Setup = nameof(Setup);
public const string LoadData = nameof(LoadData);
public const string Features = nameof(Features);
public const string Complete = nameof(Complete);
}
4. Create Tutorial Steps
Basic step:
[TutorialStep(StepIds.Welcome,
Group = MyGroup.Introduction,
Position = TooltipPosition.Center,
Order = 0)]
public class WelcomeStep : ITutorialStepContent
{
public string Title => "Welcome!";
public string Description => "Let's get started with the tutorial.";
}
Step with user action and auto-advance:
[TutorialStep(StepIds.Setup,
Group = MyGroup.Setup,
Position = TooltipPosition.Right,
RequiresUserAction = true,
ClickThroughMode = ClickThroughMode.TargetOnly,
Order = 0)]
[TutorialTarget("SetupButton")]
[AutoAdvanceOn(MyTrigger.SetupCompleted)]
public class SetupStep : ITutorialStepContent
{
public string Title => "Complete Setup";
public string Description => "Click the Setup button to configure your account.";
}
Skippable step:
[TutorialStep(StepIds.LoadData,
Group = MyGroup.Setup,
RequiresUserAction = true,
ClickThroughMode = ClickThroughMode.TargetOnly,
Order = 1)]
[TutorialTarget("LoadButton")]
[AutoAdvanceOn(MyTrigger.DataLoaded)]
public class LoadDataStep : ITutorialStepContent, ITutorialStepSkippable<MyAppContext>
{
public string Title => "Load Data";
public string Description => "Click Load to fetch your data.";
// Skip if data already exists
public bool CanSkip(MyAppContext context) => context.HasData;
public string SkipTitle => "Data Already Loaded";
public string SkipDescription => "You already have data. Click Next to continue.";
}
5. Configure Transitions
Create a static class with transition configuration:
public static class TutorialSetup
{
public static void ConfigureTransitions(TransitionBuilder<MyAppContext> builder)
{
// Conditional transition
builder.From(StepIds.Welcome)
.GoToIf(StepIds.Features, ctx => ctx.HasCompletedSetup)
.GoTo(StepIds.Setup);
// Simple transition
builder.From(StepIds.Setup)
.GoTo(StepIds.LoadData);
// Skip transition for skippable step
builder.From(StepIds.LoadData)
.SkipTo(StepIds.Features) // Where Next goes when CanSkip returns true
.GoTo(StepIds.Features); // Normal transition after auto-advance
// Custom back target
builder.From(StepIds.Features)
.BackTo(StepIds.Welcome) // Override default back navigation
.GoTo(StepIds.Complete);
// Disable back navigation
builder.From(StepIds.Complete)
.DisableBack()
.GoTo(StepIds.Complete); // Stay on last step
}
}
6. Register Services
services.AddGuidely<MyAppContext, MyTrigger, MyGroup>(builder =>
{
builder.ScanStepsFromAssembly(typeof(WelcomeStep).Assembly);
builder.ConfigureTransitions(TutorialSetup.ConfigureTransitions);
builder.SetStartStep(StepIds.Welcome);
builder.EnableAutoStart(); // Optional: auto-start tutorial on first run
// Map groups to views for restart-from-current-view
builder.MapGroupToView(MyGroup.Setup, ctx => ctx.CurrentPage == "Setup");
builder.MapGroupToView(MyGroup.Features, ctx => ctx.CurrentPage == "Features");
});
7. Initialize and Update Context
Initialize the tutorial service on application startup:
// In your application startup (e.g., MainViewModel, App.xaml.cs)
await tutorialService.InitializeAsync();
// Set initial context based on current app state
tutorialService.UpdateContext(ctx => ctx with
{
HasCompletedSetup = true,
CurrentPage = "Dashboard"
});
Update context as the application state changes:
// Update context when app state changes
tutorialService.UpdateContext(ctx => ctx with
{
HasData = true
});
// Or combine with trigger firing for auto-advance
tutorialService.FireTrigger(MyTrigger.SetupCompleted,
ctx => ctx with { HasCompletedSetup = true });
Core Concepts
Tutorial Context
The context is an immutable record that captures application state. The tutorial service uses this state to evaluate conditional transitions and determine which step to show next.
public record MyAppContext : TutorialContextBase
{
public bool IsLoggedIn { get; init; }
public int ItemCount { get; init; }
public string CurrentPage { get; init; } = string.Empty;
}
Update context using immutable with expressions:
tutorialService.UpdateContext(ctx => ctx with { IsLoggedIn = true });
Tutorial Steps
Steps are classes decorated with [TutorialStep] that implement ITutorialStepContent:
[TutorialStep("MyStep",
Group = MyGroup.Features,
Position = TooltipPosition.Bottom,
RequiresUserAction = true,
ClickThroughMode = ClickThroughMode.TargetOnly,
Order = 1)]
[TutorialTarget("MyButton")]
[AutoAdvanceOn(MyTrigger.ButtonClicked)]
public class MyStep : ITutorialStepContent
{
public string Title => "Click the Button";
public string Description => "Click the highlighted button to continue.";
}
Type-Safe Step IDs
Use static string constants for compile-time validation:
public static class StepIds
{
public const string Welcome = nameof(Welcome);
public const string Setup = nameof(Setup);
public const string Features = nameof(Features);
public const string Complete = nameof(Complete);
}
Transitions
Configure step flow using the fluent TransitionBuilder:
// Unconditional transition
builder.From(StepIds.Welcome)
.GoTo(StepIds.Setup);
// Conditional transition (evaluated first)
builder.From(StepIds.Welcome)
.GoToIf(StepIds.Features, ctx => ctx.HasCompletedSetup)
.GoTo(StepIds.Setup); // Fallback if condition is false
// Skip transition (used when step is skippable)
builder.From(StepIds.Setup)
.SkipTo(StepIds.Features) // Where Next goes when CanSkip returns true
.GoTo(StepIds.Features); // Normal transition after auto-advance
// Explicit back target (overrides history-based back)
builder.From(StepIds.Summary)
.BackTo(StepIds.Overview)
.GoTo(StepIds.Complete);
// Disable back navigation for a step
builder.From(StepIds.Setup)
.DisableBack()
.GoTo(StepIds.Features);
Auto-Advance Triggers
Steps can auto-advance when triggers are fired:
// Simple trigger-based auto-advance
[AutoAdvanceOn(MyTrigger.ButtonClicked)]
public class ClickButtonStep : ITutorialStepContent { ... }
// Complex conditional auto-advance
[AutoAdvanceOn(MyTrigger.DataLoaded)]
public class WaitForDataStep : ITutorialStepContent, IConditionalAutoAdvance<MyAppContext, MyTrigger>
{
public bool ShouldAutoAdvance(MyTrigger trigger, MyAppContext? previous, MyAppContext current)
=> trigger == MyTrigger.DataLoaded && current.ItemCount > 0;
}
Fire triggers from your application:
tutorialService.FireTrigger(MyTrigger.ButtonClicked);
// Or with context update in same atomic operation
tutorialService.FireTrigger(MyTrigger.DataLoaded, ctx => ctx with { ItemCount = 5 });
Skippable Steps
Steps that require user action can show alternate content and a Next button when their goal is already achieved. This is useful for tutorial reruns where some data already exists.
Implement ITutorialStepSkippable<TContext> on the step class:
[TutorialStep(StepIds.RefreshData,
RequiresUserAction = true,
ClickThroughMode = ClickThroughMode.TargetOnly)]
[TutorialTarget("RefreshButton")]
[AutoAdvanceOn(MyTrigger.DataRefreshed)]
public class RefreshDataStep : ITutorialStepContent, ITutorialStepSkippable<MyAppContext>
{
public string Title => "Refresh Data";
public string Description => "Click Refresh to load the latest data.";
// When CanSkip returns true, the ViewModel shows SkipTitle/SkipDescription
// and displays the Next button even though RequiresUserAction is true
public bool CanSkip(MyAppContext context) => context.HasData;
public string SkipTitle => "Refresh Data";
public string SkipDescription => "You already have data loaded. Click Next to continue, or Refresh to update.";
}
Configure the skip target in transitions using SkipTo():
builder.From(StepIds.RefreshData)
.SkipTo(StepIds.NextSection) // Where Next goes when step is skippable
.GoTo(StepIds.WaitForRefresh); // Normal transition after auto-advance
When CanSkip returns true:
- The step shows
SkipTitleandSkipDescriptioninstead of the normal content - The Next button appears even if
RequiresUserAction = true - Clicking Next navigates to the
SkipTotarget - Auto-advance still works if the user performs the action
- Back navigation skips over steps whose
CanSkipreturns true and stops at group boundaries
Back Navigation
Back navigation is group-bounded and skip-aware by default:
- The back button is hidden when at the first step of a group (group boundary)
- Steps whose
CanSkipreturns true are automatically skipped when going back
For custom back behavior, use BackTo() and DisableBack() in the transition builder:
// Explicit back target — overrides history-based back
builder.From(StepIds.Summary)
.BackTo(StepIds.Overview)
.GoTo(StepIds.Complete);
// Disable back navigation entirely for a step
builder.From(StepIds.Confirmation)
.DisableBack()
.GoTo(StepIds.Done);
Group-to-View Mapping
Map tutorial groups to application views/pages so that restart begins at the relevant group:
builder.MapGroupToView(MyGroup.Settings, ctx => ctx.CurrentPage == "Settings");
builder.MapGroupToView(MyGroup.Dashboard, ctx => ctx.CurrentPage == "Dashboard");
When RestartFromCurrentViewAsync() is called, the service evaluates these conditions against the current context and restarts from the matching group. If no group matches, it falls back to a full restart.
Groups
Groups organize steps into logical sections and determine step ordering:
public enum MyGroup
{
Introduction, // Steps with Group = 0
Setup, // Steps with Group = 1
Features, // Steps with Group = 2
Complete // Steps with Group = 3
}
API Reference
ITutorialService<TContext, TTrigger, TGroup>
Main service interface for controlling the tutorial.
| Property | Type | Description |
|---|---|---|
CurrentStepId |
string |
ID of the current step |
CurrentStep |
TutorialStepMetadata? |
Metadata for the current step |
CurrentGroup |
TGroup |
Group of the current step |
IsTutorialActive |
bool |
Whether tutorial is running |
IsTutorialCompleted |
bool |
Whether tutorial is finished |
Context |
TContext |
Current tutorial context |
CurrentStepNumber |
int |
Current step number (1-based) |
TotalSteps |
int |
Total number of steps |
CanGoBack |
bool |
Whether back navigation is available (within current group, considering skippable steps) |
IsCurrentStepSkippable |
bool |
Whether the current step can be skipped |
| Method | Description |
|---|---|
InitializeAsync() |
Load persisted state and initialize |
StartTutorial() |
Start or restart the tutorial |
AdvanceStep() |
Move to the next step |
GoBack() |
Go to the previous non-skippable step within the current group |
SkipStep() |
Navigate using the configured skip transition |
SkipTutorial() |
Skip the tutorial entirely |
RestartTutorialAsync() |
Restart from the beginning |
RestartFromCurrentViewAsync() |
Restart from the group matching the current view |
FireTrigger(trigger, contextUpdate?) |
Fire a trigger with optional context update |
UpdateContext(update) |
Update context without firing a trigger |
RestartGroup(group) |
Restart from the beginning of a group |
Attributes
[TutorialStep]
| Property | Type | Default | Description |
|---|---|---|---|
Id |
string |
Required | Unique step identifier |
Group |
object? |
null |
Group enum value (e.g., MyGroup.Introduction) |
Position |
TooltipPosition |
Bottom |
Tooltip position relative to target |
RequiresUserAction |
bool |
false |
Hides Next button when true |
ClickThroughMode |
ClickThroughMode |
None |
Overlay interaction mode |
Order |
int |
0 |
Order within group |
[TutorialTarget]
Specifies which UI element(s) to highlight. Can be applied multiple times for different targets.
[TutorialTarget("SaveButton")]
[TutorialTarget("CancelButton")]
Multiple Elements with Same Target Name: When multiple UI elements share the same target name (e.g., buttons in an ItemsControl/ListView template), all matching elements will be highlighted simultaneously. This is useful for highlighting all instances of a repeated element.
WPF example:
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="Refresh"
guidely:TutorialTarget.Element="RefreshButton" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
[AutoAdvanceOn]
Specifies triggers that can cause auto-advance. Can be applied multiple times.
[AutoAdvanceOn(MyTrigger.ItemSaved)]
[AutoAdvanceOn(MyTrigger.ItemDeleted)]
Enums
TooltipPosition
public enum TooltipPosition
{
Top,
Bottom,
Left,
Right,
Center
}
ClickThroughMode
public enum ClickThroughMode
{
None, // Overlay blocks all interaction
TargetOnly, // Only target element(s) can be clicked
All // All elements can be clicked through
}
ITutorialPersistence
Implement this interface to persist tutorial state:
public class MyPersistence : ITutorialPersistence
{
public async Task<TutorialState?> LoadStateAsync(CancellationToken ct = default)
{
// Load from database, file, etc.
}
public async Task SaveStateAsync(TutorialState state, CancellationToken ct = default)
{
// Save to database, file, etc.
}
}
Register before AddGuidely:
services.AddGuidelyPersistence<MyPersistence>();
services.AddGuidely<...>(...);
Dependency Injection
AddGuidely<TContext, TTrigger, TGroup>
Registers the tutorial service, configuration, and view model.
services.AddGuidely<MyAppContext, MyTrigger, MyGroup>(builder =>
{
builder.ScanStepsFromAssembly(typeof(WelcomeStep).Assembly);
builder.ConfigureTransitions(MySetup.ConfigureTransitions);
builder.SetStartStep(StepIds.Welcome);
builder.EnableAutoStart(); // Optional: auto-start on first run
builder.UseStepFactory((sp, type) => sp.GetRequiredService(type)); // Optional
});
Builder Methods
| Method | Description |
|---|---|
ScanStepsFromAssembly(assembly) |
Scans an assembly for step classes marked with [TutorialStep] |
ConfigureTransitions(action) |
Configures transitions between steps using TransitionBuilder |
SetStartStep(stepId) |
Sets the starting step ID |
EnableAutoStart() |
Enables automatic tutorial start on first run (no persisted state) |
UseStepFactory(factory) |
Sets a custom factory for creating step instances |
MapGroupToView(group, condition) |
Maps a group to a view condition for restart-from-current-view |
AddGuidelyPersistence<TPersistence>
Registers a custom persistence implementation:
services.AddGuidelyPersistence<MyPersistence>();
// Or with factory
services.AddGuidelyPersistence(sp => new MyPersistence(sp.GetRequiredService<IDb>()));
Complete Example
// Context
public record OnboardingContext : TutorialContextBase
{
public bool HasProfile { get; init; }
public bool HasConnectedAccount { get; init; }
}
// Triggers
public enum OnboardingTrigger
{
ProfileCreated,
AccountConnected
}
// Groups
public enum OnboardingGroup
{
Welcome,
Profile,
Account,
Done
}
// Step IDs
public static class StepIds
{
public const string Welcome = nameof(Welcome);
public const string CreateProfile = nameof(CreateProfile);
public const string ConnectAccount = nameof(ConnectAccount);
public const string AllDone = nameof(AllDone);
}
// Steps
[TutorialStep(StepIds.Welcome, Group = OnboardingGroup.Welcome, Position = TooltipPosition.Center)]
public class WelcomeStep : ITutorialStepContent
{
public string Title => "Welcome!";
public string Description => "Let's set up your account.";
}
[TutorialStep(StepIds.CreateProfile,
Group = OnboardingGroup.Profile,
Position = TooltipPosition.Right,
RequiresUserAction = true,
ClickThroughMode = ClickThroughMode.TargetOnly)]
[TutorialTarget("ProfileButton")]
[AutoAdvanceOn(OnboardingTrigger.ProfileCreated)]
public class CreateProfileStep : ITutorialStepContent, IConditionalAutoAdvance<OnboardingContext, OnboardingTrigger>
{
public string Title => "Create Your Profile";
public string Description => "Click to create your profile.";
public bool ShouldAutoAdvance(OnboardingTrigger trigger, OnboardingContext? prev, OnboardingContext curr)
=> trigger == OnboardingTrigger.ProfileCreated && curr.HasProfile;
}
// Transitions
public static class OnboardingSetup
{
public static void ConfigureTransitions(TransitionBuilder<OnboardingContext> builder)
{
builder.From(StepIds.Welcome)
.GoToIf(StepIds.ConnectAccount, ctx => ctx.HasProfile)
.GoTo(StepIds.CreateProfile);
builder.From(StepIds.CreateProfile)
.GoTo(StepIds.ConnectAccount);
builder.From(StepIds.ConnectAccount)
.GoTo(StepIds.AllDone);
}
}
// Registration
services.AddGuidely<OnboardingContext, OnboardingTrigger, OnboardingGroup>(builder =>
{
builder.ScanStepsFromAssembly(typeof(WelcomeStep).Assembly);
builder.ConfigureTransitions(OnboardingSetup.ConfigureTransitions);
builder.SetStartStep(StepIds.Welcome);
builder.EnableAutoStart(); // Start tutorial automatically on first run
});
License
Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- CommunityToolkit.Mvvm (>= 8.4.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Guidely.Core:
| Package | Downloads |
|---|---|
|
Guidely.WPF
WPF controls and overlays for Guidely interactive tutorials |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 113 | 2/11/2026 |