When the UI Knows Too Much
Single Responsibility · 2026-04-24
A practical look at how UI code can gradually take ownership of business and workflow decisions, and how keeping the UI focused on presentation and interaction makes systems easier to change, test, and reason about.
When the UI Knows Too Much
The UI should reflect the state of the system, not secretly define the rules of the system.
1. Introduction
The user interface is often the easiest place to add a small piece of logic.
A button needs to be hidden. A list needs to exclude a few items. A field needs to be disabled. A warning needs to appear under certain conditions. The UI already has access to the data, and the change can often be made quickly.
That is why UI logic tends to grow gradually.
At first, this may seem harmless. The system still works. The screen looks correct. The user sees the right options.
But over time, the UI can become more than a presentation layer. It starts to decide which records are visible, which actions are allowed, which workflow steps are valid, and which business rules apply in a specific situation.
When that happens, the UI no longer only presents the system. It becomes part of the system’s decision-making.
That is where the boundary starts to blur.
2. What the UI should normally own
The UI has important responsibilities. Keeping business decisions out of the UI does not mean making the UI passive or unimportant.
The UI normally owns presentation and interaction.
It decides how information is arranged, which controls are used, how the user moves through a screen, how validation messages are displayed, and how feedback is shown.
For example, the UI may decide:
- Whether a command appears as a button, menu item, or keyboard shortcut.
- Whether a list is shown as cards, rows, or grouped sections.
- How dates, labels, and messages are formatted for the user.
- How loading, empty, and error states are presented.
- How the user initiates an application operation.
Those are presentation concerns.
The UI may also perform simple input checks that improve the user experience. For example, it can show that a required field is empty before the request is submitted. But that should normally be treated as user guidance, not as the only enforcement of the rule.
A useful distinction is this:
The UI may help the user avoid invalid actions, but it should not be the only place where invalid actions are prevented.
The real rule should still live in the domain, application layer, or another clearly defined owner.
3. What happens when the UI owns business behavior
When the UI owns too much behavior, the system can become difficult to reason about.
The most obvious problem is duplication. A rule first appears in one screen. Later, the same rule is needed in another screen, an API endpoint, a background job, or an import process. If the rule only exists in the UI, it has to be copied or reimplemented elsewhere.
The second problem is inconsistency. Different screens may implement the same rule slightly differently. One screen hides a command. Another disables it. A third still allows the action because the rule was never added there.
The third problem is weak protection. UI logic can prevent a user from clicking a button, but it does not necessarily protect the system from invalid operations. Other callers may still reach the application service directly.
The fourth problem is testing. If business rules are embedded in UI code, testing them may require UI-level tests. Those tests are often slower, more fragile, and more difficult to maintain than tests against domain or application behavior.
The fifth problem is hidden ownership. Developers may not know whether a rule belongs to the UI, application layer, domain model, or repository. If the UI has already become the place where special cases are handled, the next rule is likely to be added there as well.
The UI still looks like a UI, but it has become a decision layer.
4. Example: Visibility rules in the UI
Consider a page that shows articles to visitors:
var articles = await _articleRepository.GetAllAsync(ct);
VisibleArticles = articles
.Where(article => article.IsPublished)
.OrderByDescending(article => article.PublishedUtc)
.ToList();
This may work, but the UI now owns the visibility rule.
It knows that unpublished articles should be hidden. It knows which property defines visibility. It knows how publication date affects ordering. If another page also needs published articles, that logic may be repeated.
A clearer design is usually to make the use case explicit:
var articles = await _articleService.GetPublishedArticlesAsync(ct);
or, depending on the architecture:
var articles = await _articleRepository.GetPublishedArticlesAsync(ct);
The important point is not the exact class name. The important point is that visitor visibility should have one clear owner.
The UI can still decide how to present the returned articles. It can choose layout, grouping, empty-state text, and interaction behavior.
But it should not have to define what makes an article visible to visitors.
5. Example: Workflow rules in the UI
The same problem appears with actions.
Suppose an admin screen allows an article to be published only when it has a title and content.
A UI-only implementation might look like this:
PublishButton.IsEnabled =
!string.IsNullOrWhiteSpace(Article.Title) &&
!string.IsNullOrWhiteSpace(Article.ContentMarkdown);
This is useful as presentation behavior. It gives the user immediate feedback.
But it should not be the only place where the publishing rule exists.
If the application service also allows this:
article.IsPublished = true;
article.PublishedUtc = _clock.UtcNow;
await _articleRepository.SaveAsync(article, ct);
then the system relies on the UI to prevent invalid publishing. That is fragile.
A better design is to let the UI reflect whether publishing appears possible, while the actual rule is enforced by the application or domain layer:
public void Publish(DateTime publishedUtc)
{
if (string.IsNullOrWhiteSpace(Title))
{
throw new InvalidArticleStateException(
"An article must have a title before it can be published.");
}
if (string.IsNullOrWhiteSpace(ContentMarkdown))
{
throw new InvalidArticleStateException(
"An article must have content before it can be published.");
}
IsPublished = true;
PublishedUtc = publishedUtc;
}
The UI may still disable the button. That is fine.
But the button state is now a convenience for the user, not the only protection for the system.
6. Moving decisions out of the UI
Moving decisions out of the UI does not mean removing all logic from the UI.
The goal is to separate presentation decisions from system decisions.
A useful question is:
Is this logic deciding how something is shown, or is it deciding what the system allows?
If the logic is about visual presentation, it probably belongs in the UI.
If the logic decides whether an operation is allowed, it should usually be enforced outside the UI.
If the logic decides which records are part of a use case, it may belong in the repository or application layer.
If the logic represents a business rule, it may belong in the domain model or a policy.
For example:
PublishButton.IsEnabled = Article.CanPublish;
can be acceptable UI behavior if CanPublish is a value provided by the application layer or domain model.
But this is less desirable:
PublishButton.IsEnabled =
!string.IsNullOrWhiteSpace(Article.Title) &&
!string.IsNullOrWhiteSpace(Article.ContentMarkdown) &&
CurrentUser.Role == "Editor" &&
!Article.IsArchived;
The second version mixes presentation with publishing rules, authorization rules, and article state rules.
A clearer approach is to let the application layer return a view model or result that already expresses what the UI needs to know:
public sealed class ArticleEditorViewModel
{
public required string Title { get; init; }
public required string ContentMarkdown { get; init; }
public required bool CanPublish { get; init; }
public required bool CanEdit { get; init; }
}
Then the UI can remain simple:
PublishButton.IsEnabled = ViewModel.CanPublish;
The decision still exists. It has not disappeared.
It has simply moved to a place where it can be tested, reused, and understood without opening the UI.
7. Relation to SOLID and common patterns
This topic is closely related to the Single Responsibility Principle.
A UI component should not change because the layout changed, because a business rule changed, because an authorization rule changed, and because a persistence detail changed. Those are different reasons to change.
The Dependency Inversion Principle is also relevant. The UI should normally depend on application-level abstractions or use cases, not directly on repositories, file stores, HTTP clients, or database-specific details.
Patterns such as MVC, MVP, and MVVM exist partly to manage this boundary. They separate presentation, interaction, state, and application behavior so that the UI does not become the only place where the system can be understood.
Command-based designs can also help. A button click should often map to an application operation, such as:
await _publishArticleHandler.HandleAsync(command, ct);
rather than directly manipulating persistence state from the UI.
But patterns are only useful when they clarify responsibility.
An MVVM application can still have too much business logic in the view model. An MVC application can still put too much behavior in the controller. A command handler can still become overloaded.
The pattern is not the goal. Clear ownership is the goal.
8. Warning signs that the UI knows too much
The UI may know too much if several of these signs appear:
- The same business rule is implemented in more than one screen.
- A button being disabled is the only thing preventing an invalid operation.
- UI code calls repositories or infrastructure services directly.
- UI components contain business rules rather than presentation rules.
- View models contain large amounts of workflow or domain behavior.
- Different screens apply the same rule differently.
- Background jobs, imports, or APIs must duplicate rules that already exist in the UI.
- UI tests are required to verify core business behavior.
- Small rule changes require edits in several screens.
- Developers are unsure whether a rule belongs in the UI or in the application layer.
The most important sign is not the presence of logic in the UI. Some logic belongs there.
The important warning sign is that the UI has become the owner of rules that should apply even when the UI is not involved.
9. Improving UI boundaries gradually
UI boundaries usually become unclear one small change at a time. They should usually be improved the same way.
A large rewrite of the UI is rarely the best first step. It is often safer to identify one decision that does not really belong there and move it to a clearer home.
A gradual approach could look like this:
- Find one rule currently implemented in the UI.
- Ask whether the rule should also apply outside that screen.
- Move the rule to the domain, application layer, repository, or policy that should own it.
- Keep the UI behavior as a reflection of that rule.
- Add tests around the rule outside the UI.
- Remove duplicated versions from other screens.
For example, if several screens decide whether an article can be published, create one application-level or domain-level decision and let the screens consume the result.
The UI can still be responsive. It can still guide the user. It can still show immediate feedback.
But the rule now has one home.
A useful test is to ask:
If this screen did not exist, would the rule still matter?
If the answer is yes, the UI probably should not be the only owner of the rule.
10. Conclusion
The UI is an important part of the system. It is where the user experiences the application, and it often determines whether the software feels clear or frustrating.
But the UI should not secretly define the rules of the system.
It should present state, collect input, guide the user, and initiate application operations. It should not become the hidden owner of business rules, workflow decisions, persistence behavior, or authorization logic.
When the UI knows too much, changes become harder to localize, rules become easier to duplicate, and important behavior becomes harder to test outside the screen.
When the UI reflects decisions owned elsewhere, the system becomes easier to change, easier to test, and easier to explain.
Need a second opinion on a .NET architecture decision?
I offer practical architecture and maintainability advisory for .NET teams working on long-lived business applications.
Want occasional updates?
Subscribe to receive occasional practical notes on .NET architecture, maintainable software, project progress, and new articles.